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

View File

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

View File

@ -154,7 +154,9 @@ const Menu = {
render() { render() {
const props = { ...getOptionProps(this), ...this.$attrs }; 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 = { const subPopupMenuProps = {
...props, ...props,
itemIcon: getComponent(this, 'itemIcon', 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 { getSelectKeys, preventDefaultEvent, saveRef } from './util';
import { cloneElement } from '../_util/vnode'; import { cloneElement } from '../_util/vnode';
import BaseMixin from '../_util/BaseMixin'; import BaseMixin from '../_util/BaseMixin';
import { getSlotOptions, findDOMNode } from '../_util/props-util'; import { findDOMNode } from '../_util/props-util';
export default { export default {
name: 'DropdownMenu', name: 'DropdownMenu',
@ -151,10 +151,11 @@ export default {
}; };
clonedMenuItems = menuItems.map(item => { clonedMenuItems = menuItems.map(item => {
debugger; if (item.type.isMenuItemGroup) {
if (getSlotOptions(item).isMenuItemGroup) { const children = (item.children?.default() || []).map(clone);
const children = item.componentOptions.children.map(clone); const newItem = cloneElement(item);
return cloneElement(item, { children }); newItem.children = { ...item.children, default: () => children };
return newItem;
} }
return clone(item); return clone(item);
}); });
@ -182,9 +183,8 @@ export default {
{...menuProps} {...menuProps}
selectedKeys={selectedKeys} selectedKeys={selectedKeys}
prefixCls={`${prefixCls}-menu`} prefixCls={`${prefixCls}-menu`}
> children={clonedMenuItems}
{clonedMenuItems} ></Menu>
</Menu>
); );
} }
return null; return null;

View File

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

View File

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

View File

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

View File

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

View File

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