feat: select

pull/2510/head
tanjinzhou 2020-06-29 18:43:43 +08:00
parent 5a0bb6d50e
commit 106b82ca67
10 changed files with 83 additions and 94 deletions

View File

@ -95,7 +95,7 @@ const getSlotOptions = ele => {
};
const findDOMNode = instance => {
let node = instance.$el;
while (!node.tagName) {
while (node && !node.tagName) {
node = node.nextSibling;
}
return node;
@ -200,7 +200,8 @@ const getAllProps = ele => {
return props;
};
const getPropsData = vnode => {
const getPropsData = ins => {
const vnode = ins.$ ? ins.$ : ins;
const res = {};
const originProps = vnode.props || {};
const props = {};

View File

@ -107,10 +107,6 @@ const Select = {
choiceTransitionName: PropTypes.string.def('zoom'),
},
propTypes: SelectPropTypes,
// model: {
// prop: 'value',
// event: 'change',
// },
setup() {
return {
configProvider: inject('configProvider', ConfigConsumerProps),

View File

@ -154,7 +154,9 @@ const Menu = {
render() {
const props = { ...getOptionProps(this), ...this.$attrs };
props.class += ` ${props.prefixCls}-root`;
props.class = props.class
? `${props.class} ${props.prefixCls}-root`
: `${props.prefixCls}-root`;
const subPopupMenuProps = {
...props,
itemIcon: getComponent(this, 'itemIcon', props),

View File

@ -5,7 +5,7 @@ import scrollIntoView from 'dom-scroll-into-view';
import { getSelectKeys, preventDefaultEvent, saveRef } from './util';
import { cloneElement } from '../_util/vnode';
import BaseMixin from '../_util/BaseMixin';
import { getSlotOptions, findDOMNode } from '../_util/props-util';
import { findDOMNode } from '../_util/props-util';
export default {
name: 'DropdownMenu',
@ -151,10 +151,11 @@ export default {
};
clonedMenuItems = menuItems.map(item => {
debugger;
if (getSlotOptions(item).isMenuItemGroup) {
const children = item.componentOptions.children.map(clone);
return cloneElement(item, { children });
if (item.type.isMenuItemGroup) {
const children = (item.children?.default() || []).map(clone);
const newItem = cloneElement(item);
newItem.children = { ...item.children, default: () => children };
return newItem;
}
return clone(item);
});
@ -182,9 +183,8 @@ export default {
{...menuProps}
selectedKeys={selectedKeys}
prefixCls={`${prefixCls}-menu`}
>
{clonedMenuItems}
</Menu>
children={clonedMenuItems}
></Menu>
);
}
return null;

View File

@ -1,3 +1,4 @@
import {TransitionGroup} from 'vue';
import KeyCode from '../_util/KeyCode';
import PropTypes from '../_util/vue-types';
import classnames from 'classnames';
@ -8,13 +9,10 @@ import Option from './Option';
import OptGroup from './OptGroup';
import {
hasProp,
getSlotOptions,
getPropsData,
getValueByProp as getValue,
getComponent,
getEvents,
getClass,
getAttrs,
getOptionProps,
} from '../_util/props-util';
import getTransitionProps from '../_util/getTransitionProps';
@ -106,10 +104,6 @@ const Select = {
// onDeselect: noop,
// onInputKeydown: noop,
},
// model: {
// prop: 'value',
// event: 'change',
// },
created() {
this.saveInputRef = saveRef(this, 'inputRef');
this.saveInputMirrorRef = saveRef(this, 'inputMirrorRef');
@ -175,7 +169,7 @@ const Select = {
__propsSymbol__() {
Object.assign(this.$data, this.getDerivedState(getOptionProps(this), this.$data));
},
'$data._inputValue'(val) {
_inputValue(val) {
this.$data._mirrorInputValue = val;
},
},
@ -232,8 +226,8 @@ const Select = {
if (!child.data || child.data.slot !== undefined) {
return;
}
if (getSlotOptions(child).isSelectOptGroup) {
this.getOptionsFromChildren(child.componentOptions.children, options);
if (child.type?.isSelectOptGroup) {
this.getOptionsFromChildren(child.children?.default(), options);
} else {
options.push(child);
}
@ -787,32 +781,31 @@ const Select = {
_getInputElement() {
const props = this.$props;
const { _inputValue: inputValue, _mirrorInputValue } = this.$data;
const attrs = getAttrs(this);
const attrs = this.$attrs;
const defaultInput = <input id={attrs.id} autoComplete="off" />;
const inputElement = props.getInputElement ? props.getInputElement() : defaultInput;
const inputCls = classnames(getClass(inputElement), {
const inputCls = classnames(inputElement.class, {
[`${props.prefixCls}-search__field`]: true,
});
const inputEvents = getEvents(inputElement);
// https://github.com/ant-design/ant-design/issues/4992#issuecomment-281542159
// Add space to the end of the inputValue as the width measurement tolerance
inputElement.data = inputElement.data || {};
return (
<div class={`${props.prefixCls}-search__field__wrap`} onClick={this.inputClick}>
{cloneElement(inputElement, {
disabled: props.disabled,
value: inputValue,
...(inputElement.data.attrs || {}),
...(inputElement.props || {}),
disabled: props.disabled,
value: inputValue,
class: inputCls,
ref: this.saveInputRef,
directives: [
{
name: 'ant-input',
},
],
// directives: [
// {
// name: 'ant-input',
// },
// ],
onInput: this.onInputChange,
onKeydown: chaining(
this.onInputKeydown,
@ -1099,6 +1092,7 @@ const Select = {
const vls = this.getVLForOnChange(value);
const options = this.getOptionsBySingleValue(value);
this._valueOptions = options;
this.$emit('update:value', vls);
this.$emit('change', vls, isMultipleOrTags(this.$props) ? options : options[0]);
},
@ -1186,16 +1180,11 @@ const Select = {
const { _inputValue: inputValue } = this.$data;
const tags = props.tags;
children.forEach(child => {
const type = child.type;
warning(
typeof type === 'object' && type.isSelectOption,
'the children of `Select` should be `Select.Option` or `Select.OptGroup`, ' +
`instead of \`${getSlotOptions(child).name || getSlotOptions(child)}\`.`,
);
if (typeof type !== 'object' || !type.isSelectOption) {
if (!child) {
return;
}
if (type.isSelectOptGroup) {
const type = child.type;
if (type?.isSelectOptGroup) {
let label = getComponent(child, 'label');
let key = child.key;
if (!key && typeof label === 'string') {
@ -1211,14 +1200,14 @@ const Select = {
const childValueSub = getValuePropValue(subChild) || subChild.key;
return (
<MenuItem key={childValueSub} value={childValueSub} {...subChild.props}>
{subChild.children?.default()}
{...(subChild.children?.default())}
</MenuItem>
);
});
sel.push(
<MenuItemGroup key={key} title={label} class={child.props?.class}>
{innerItems}
{...innerItems}
</MenuItemGroup>,
);
@ -1232,7 +1221,7 @@ const Select = {
if (innerItems.length) {
sel.push(
<MenuItemGroup key={key} title={label} {...child.props}>
{innerItems}
{...innerItems}
</MenuItemGroup>,
);
}
@ -1240,6 +1229,10 @@ const Select = {
return;
}
warning(
typeof type === 'object' && type.isSelectOption,
'the children of `Select` should be `Select.Option` or `Select.OptGroup`, ',
);
const childValue = getValuePropValue(child);
@ -1416,10 +1409,10 @@ const Select = {
if (isMultipleOrTags(props) && choiceTransitionName) {
const transitionProps = getTransitionProps(choiceTransitionName, {
tag: 'ul',
afterLeave: this.onChoiceAnimationLeave,
onAfterLeave: this.onChoiceAnimationLeave,
});
innerNode = (
<transition-group {...transitionProps}>{selectedValueNodes}</transition-group>
<TransitionGroup {...transitionProps}>{selectedValueNodes}</TransitionGroup>
);
} else {
innerNode = <ul>{selectedValueNodes}</ul>;

View File

@ -1,11 +1,9 @@
// based on vc-select 9.2.2
import ProxySelect, { Select } from './Select';
import Select from './Select';
import Option from './Option';
import { SelectPropTypes } from './PropTypes';
import OptGroup from './OptGroup';
Select.Option = Option;
Select.OptGroup = OptGroup;
ProxySelect.Option = Option;
ProxySelect.OptGroup = OptGroup;
export { Select, Option, OptGroup, SelectPropTypes };
export default ProxySelect;
export default Select;

View File

@ -1,5 +1,6 @@
import { getPropsData, getSlotOptions, getKey, getComponent } from '../_util/props-util';
import { getPropsData, getComponent } from '../_util/props-util';
import { cloneElement } from '../_util/vnode';
import { isVNode, Text } from 'vue';
export function toTitle(title) {
if (typeof title === 'string') {
@ -15,8 +16,8 @@ export function getValuePropValue(child) {
if ('value' in props) {
return props.value;
}
if (getKey(child) !== undefined) {
return getKey(child);
if (child.key !== undefined) {
return child.key;
}
if (typeof child.type === 'object' && child.type.isSelectOptGroup) {
const label = getComponent(child, 'label');
@ -32,20 +33,14 @@ export function getPropValue(child, prop) {
return getValuePropValue(child);
}
if (prop === 'children') {
const newChild = child.$slots
? cloneElement(child.$slots.default)
: cloneElement(child.componentOptions.children);
if (newChild.length === 1 && !newChild[0].tag) {
return newChild[0].text;
const newChild = cloneElement(getComponent(child));
if (isVNode(newChild) && newChild.type === Text) {
return newChild.children;
}
return newChild;
}
const data = getPropsData(child);
if (prop in data) {
return data[prop];
} else {
return child.props && child.props[prop];
}
const props = getPropsData(child);
return props[prop];
}
export function isMultiple(props) {
@ -113,14 +108,14 @@ export function getLabelFromPropsValue(value, key) {
return label;
}
export function getSelectKeys(menuItems, value) {
export function getSelectKeys(menuItems = [], value) {
if (value === null || value === undefined) {
return [];
}
let selectedKeys = [];
menuItems.forEach(item => {
if (getSlotOptions(item).isMenuItemGroup) {
selectedKeys = selectedKeys.concat(getSelectKeys(item.componentOptions.children, value));
if (item.type?.isMenuItemGroup) {
selectedKeys = selectedKeys.concat(getSelectKeys(item.children?.default(), value));
} else {
const itemValue = getValuePropValue(item);
const itemKey = item.key;
@ -145,8 +140,8 @@ export function findFirstMenuItem(children) {
for (let i = 0; i < children.length; i++) {
const child = children[i];
const props = getPropsData(child);
if (getSlotOptions(child).isMenuItemGroup) {
const found = findFirstMenuItem(child.componentOptions.children);
if (child.type?.isMenuItemGroup) {
const found = findFirstMenuItem(child.children?.default());
if (found) {
return found;
}

View File

@ -5,7 +5,8 @@ import PopupInner from './PopupInner';
import LazyRenderBox from './LazyRenderBox';
import animate from '../_util/css-animation';
import BaseMixin from '../_util/BaseMixin';
import { getListeners, splitAttrs } from '../_util/props-util';
import { saveRef } from './utils';
import { getListeners, splitAttrs, findDOMNode } from '../_util/props-util';
export default {
name: 'VCTriggerPopup',
@ -36,6 +37,8 @@ export default {
data() {
this.domEl = null;
this.currentAlignClassName = undefined;
this.savePopupRef = saveRef.bind(this, 'popupInstance');
this.saveAlignRef = saveRef.bind(this, 'alignInstance');
return {
// Used for stretch
stretchChecked: false,
@ -62,13 +65,13 @@ export default {
this.setStretchSize();
});
},
beforeUnmount() {
if (this.$el.parentNode) {
this.$el.parentNode.removeChild(this.$el);
} else if (this.$el.remove) {
this.$el.remove();
}
},
// beforeUnmount() {
// if (this.$el.parentNode) {
// this.$el.parentNode.removeChild(this.$el);
// } else if (this.$el.remove) {
// this.$el.remove();
// }
// },
methods: {
onAlign(popupDomNode, align) {
const props = this.$props;
@ -111,7 +114,7 @@ export default {
},
getPopupDomNode() {
return this.$refs.popupInstance ? this.$refs.popupInstance.$el : null;
return findDOMNode(this.popupInstance);
},
getTargetElement() {
@ -158,6 +161,7 @@ export default {
} ${currentAlignClassName}`;
},
getPopupElement() {
const { savePopupRef } = this;
const { $props: props, $attrs, $slots, getTransitionName } = this;
const { stretchChecked, targetHeight, targetWidth } = this.$data;
const { style = {} } = $attrs;
@ -197,8 +201,8 @@ export default {
if (!stretchChecked) {
// sizeStyle.visibility = 'hidden'
setTimeout(() => {
if (this.$refs.alignInstance) {
this.$refs.alignInstance.forceAlign();
if (this.alignInstance) {
this.alignInstance.forceAlign();
}
}, 0);
}
@ -209,7 +213,7 @@ export default {
// hiddenClassName,
class: className,
...onEvents,
ref: 'popupInstance',
ref: savePopupRef,
style: { ...sizeStyle, ...popupStyle, ...style, ...this.getZIndexStyle() },
};
let transitionProps = {
@ -221,13 +225,13 @@ export default {
const transitionEvent = {
onBeforeEnter: () => {
// el.style.display = el.__vOriginalDisplay
// this.$refs.alignInstance.forceAlign();
// this.alignInstance.forceAlign();
},
onEnter: (el, done) => {
// render vue animate classclass transition
this.$nextTick(() => {
if (this.$refs.alignInstance) {
this.$refs.alignInstance.$nextTick(() => {
if (this.alignInstance) {
this.alignInstance.$nextTick(() => {
this.domEl = el;
animate(el, `${transitionName}-enter`, done);
});
@ -258,7 +262,7 @@ export default {
<Align
target={this.getAlignTarget()}
key="popup"
ref="alignInstance"
ref={this.saveAlignRef}
monitorWindowResize
align={align}
onAlign={this.onAlign}
@ -275,7 +279,7 @@ export default {
v-show={visible}
target={this.getAlignTarget()}
key="popup"
ref="alignInstance"
ref={this.saveAlignRef}
monitorWindowResize
disabled={!visible}
align={align}

View File

@ -318,7 +318,7 @@ export default {
return;
}
const target = event.target;
const root = this.$el;
const root = findDOMNode(this);
if (!contains(root, target) && !this.hasPopupMouseDown) {
this.close();
}
@ -417,11 +417,7 @@ export default {
...mouseProps,
ref: this.savePopup,
};
return (
<Popup ref="popup" {...popupProps}>
{getComponent(self, 'popup')}
</Popup>
);
return <Popup {...popupProps}>{getComponent(self, 'popup')}</Popup>;
},
getContainer() {
@ -434,7 +430,7 @@ export default {
popupContainer.style.left = '0';
popupContainer.style.width = '100%';
const mountNode = props.getPopupContainer
? props.getPopupContainer(this.$el, dialogContext)
? props.getPopupContainer(findDOMNode(this), dialogContext)
: props.getDocument().body;
mountNode.appendChild(popupContainer);
this.popupContainer = popupContainer;

View File

@ -25,3 +25,7 @@ export function getAlignPopupClassName(builtinPlacements, prefixCls, align, isAl
return '';
}
export function noop() {}
export function saveRef(name, component) {
this[name] = component;
}