diff --git a/components/select/__tests__/index.test.js b/components/select/__tests__/index.test.js
index e15f0c926..19fa6f06a 100644
--- a/components/select/__tests__/index.test.js
+++ b/components/select/__tests__/index.test.js
@@ -3,9 +3,11 @@ import { asyncExpect } from '@/tests/utils';
import Select from '..';
import Icon from '../../icon';
import focusTest from '../../../tests/shared/focusTest';
+import mountTest from '../../../tests/shared/mountTest';
describe('Select', () => {
focusTest(Select);
+ mountTest(Select);
it('should have default notFoundContent', async () => {
const wrapper = mount(Select, {
diff --git a/components/select/demo/custom-dropdown-menu.md b/components/select/demo/custom-dropdown-menu.md
index b1957560f..a333599ea 100644
--- a/components/select/demo/custom-dropdown-menu.md
+++ b/components/select/demo/custom-dropdown-menu.md
@@ -14,21 +14,27 @@ Customize the dropdown menu via `dropdownRender`.
-
+
e.preventDefault()" @click="addItem">
Add item
-
Jack
-
Lucy
+
{{item}}
```
diff --git a/components/select/demo/index.vue b/components/select/demo/index.vue
index 03fae3114..a8ee714e0 100644
--- a/components/select/demo/index.vue
+++ b/components/select/demo/index.vue
@@ -13,6 +13,7 @@ import SelectUsers from './select-users';
import Suffix from './suffix';
import HideSelected from './hide-selected';
import CustomDropdownMenu from './custom-dropdown-menu';
+import OptionLabelProp from './option-label-prop';
import CN from '../index.zh-CN.md';
import US from '../index.en-US.md';
@@ -54,6 +55,7 @@ export default {
+
diff --git a/components/select/demo/option-label-prop.md b/components/select/demo/option-label-prop.md
new file mode 100644
index 000000000..67472237b
--- /dev/null
+++ b/components/select/demo/option-label-prop.md
@@ -0,0 +1,60 @@
+
+#### 定制回填内容
+使用 `optionLabelProp` 指定回填到选择框的 `Option` 属性。
+
+
+
+#### Custom selection render
+Spacified the prop name of Option which will be rendered in select box.
+
+
+```tpl
+
+
+
+
+ 🇨🇳
+
+ China (中国)
+
+
+
+ 🇺🇸
+
+ USA (美国)
+
+
+
+ 🇯🇵
+
+ Japan (日本)
+
+
+
+ 🇰🇷
+
+ Korea (韩国)
+
+
+
+
+```
diff --git a/components/select/index.en-US.md b/components/select/index.en-US.md
index fbd9ab587..9f3b77e61 100644
--- a/components/select/index.en-US.md
+++ b/components/select/index.en-US.md
@@ -20,6 +20,7 @@
| dropdownMatchSelectWidth | Whether dropdown's width is same with select. | boolean | true |
| dropdownRender | Customize dropdown content | (menuNode: VNode, props) => VNode | - |
| dropdownStyle | style of dropdown menu | object | - |
+| dropdownMenuStyle | additional style applied to dropdown menu | object | - |
| filterOption | If true, filter options by input, if function, filter options against it. The function will receive two arguments, `inputValue` and `option`, if the function returns `true`, the option will be included in the filtered set; Otherwise, it will be excluded. | boolean or function(inputValue, option) | true |
| firstActiveValue | Value of action option by default | string\|string\[] | - |
| getPopupContainer | Parent Node which the selector should be rendered to. Default to `body`. When position issues happen, try to modify it into scrollable content and position it relative. | function(triggerNode) | () => document.body |
@@ -85,3 +86,9 @@
| -------- | ----------- | ------------ | ------- |
| key | | string | - |
| label | Group label | string\|slot | - |
+
+## FAQ
+
+### The dropdown is closed when click `dropdownRender` area?
+
+See the [dropdownRender example](/components/select/#components-select-demo-custom-dropdown).
diff --git a/components/select/index.jsx b/components/select/index.jsx
index 80d492a63..706c9886a 100644
--- a/components/select/index.jsx
+++ b/components/select/index.jsx
@@ -118,21 +118,13 @@ const Select = {
created() {
warning(
this.$props.mode !== 'combobox',
+ 'Select',
'The combobox mode of Select is deprecated,' +
'it will be removed in next major version,' +
'please use AutoComplete instead',
);
},
methods: {
- savePopupRef(ref) {
- this.popupRef = ref;
- },
- focus() {
- this.$refs.vcSelect.focus();
- },
- blur() {
- this.$refs.vcSelect.blur();
- },
getNotFoundContent(renderEmpty) {
const h = this.$createElement;
const notFoundContent = getComponentFromProp(this, 'notFoundContent');
@@ -144,6 +136,16 @@ const Select = {
}
return renderEmpty(h, 'Select');
},
+ savePopupRef(ref) {
+ this.popupRef = ref;
+ },
+ focus() {
+ this.$refs.vcSelect.focus();
+ },
+ blur() {
+ this.$refs.vcSelect.blur();
+ },
+
isCombobox() {
const { mode } = this;
return mode === 'combobox' || mode === SECRET_COMBOBOX_MODE_DO_NOT_USE;
@@ -171,6 +173,7 @@ const Select = {
mode,
options,
getPopupContainer,
+ showArrow,
...restProps
} = getOptionProps(this);
@@ -198,6 +201,7 @@ const Select = {
const cls = {
[`${prefixCls}-lg`]: size === 'large',
[`${prefixCls}-sm`]: size === 'small',
+ [`${prefixCls}-show-arrow`]: showArrow,
};
let { optionLabelProp } = this.$props;
@@ -234,6 +238,7 @@ const Select = {
removeIcon: finalRemoveIcon,
clearIcon: finalClearIcon,
menuItemSelectedIcon: finalMenuItemSelectedIcon,
+ showArrow,
...rest,
...modeConfig,
prefixCls,
diff --git a/components/select/index.zh-CN.md b/components/select/index.zh-CN.md
index f1e1a5230..e7fdd3131 100644
--- a/components/select/index.zh-CN.md
+++ b/components/select/index.zh-CN.md
@@ -20,6 +20,7 @@
| dropdownMatchSelectWidth | 下拉菜单和选择器同宽 | boolean | true |
| dropdownRender | 自定义下拉框内容 | (menuNode: VNode, props) => VNode | - |
| dropdownStyle | 下拉菜单的 style 属性 | object | - |
+| dropdownMenuStyle | dropdown 菜单自定义样式 | object | - |
| filterOption | 是否根据输入项进行筛选。当其为一个函数时,会接收 `inputValue` `option` 两个参数,当 `option` 符合筛选条件时,应返回 `true`,反之则返回 `false`。 | boolean or function(inputValue, option) | true |
| firstActiveValue | 默认高亮的选项 | string\|string\[] | - |
| getPopupContainer | 菜单渲染父节点。默认渲染到 body 上,如果你遇到菜单滚动定位问题,试试修改为滚动的区域,并相对其定位。 | Function(triggerNode) | () => document.body |
@@ -86,3 +87,9 @@
| ----- | ---- | --------------------------- | ------ |
| key | | string | - |
| label | 组名 | string\|\|function(h)\|slot | 无 |
+
+## FAQ
+
+### 点击 `dropdownRender` 里的内容浮层关闭怎么办?
+
+看下 [dropdownRender 例子](/components/select-cn/#components-select-demo-custom-dropdown) 里的说明。
diff --git a/components/vc-select/DropdownMenu.jsx b/components/vc-select/DropdownMenu.jsx
index 52ccfca50..ee68e56df 100644
--- a/components/vc-select/DropdownMenu.jsx
+++ b/components/vc-select/DropdownMenu.jsx
@@ -37,7 +37,7 @@ export default {
},
created() {
- this.rafInstance = { cancel: () => null };
+ this.rafInstance = null;
this.lastInputValue = this.$props.inputValue;
this.lastVisible = false;
},
@@ -60,8 +60,8 @@ export default {
this.prevVisible = this.visible;
},
beforeDestroy() {
- if (this.rafInstance && this.rafInstance.cancel) {
- this.rafInstance.cancel();
+ if (this.rafInstance) {
+ raf.cancel(this.rafInstance);
}
},
methods: {
diff --git a/components/vc-select/Select.jsx b/components/vc-select/Select.jsx
index fd8ca58d8..50235ff63 100644
--- a/components/vc-select/Select.jsx
+++ b/components/vc-select/Select.jsx
@@ -59,6 +59,11 @@ const SELECT_EMPTY_VALUE_KEY = 'RC_SELECT_EMPTY_VALUE_KEY';
const noop = () => null;
+// Where el is the DOM element you'd like to test for visibility
+function isHidden(node) {
+ return !node || node.offsetParent === null;
+}
+
function chaining(...fns) {
return function(...args) {
// eslint-disable-line
@@ -130,6 +135,13 @@ const Select = {
this.__propsSymbol__,
'Replace slots.default with props.children and pass props.__propsSymbol__',
);
+ if (props.tags && typeof props.filterOption !== 'function') {
+ const isDisabledExist = Object.keys(optionsInfo).some(key => optionsInfo[key].disabled);
+ warning(
+ !isDisabledExist,
+ 'Please avoid setting option to disabled in tags mode since user can always type text as tag.',
+ );
+ }
const state = {
_value: this.getValueFromProps(props, true), // true: use default value
_inputValue: props.combobox
@@ -191,6 +203,7 @@ const Select = {
beforeDestroy() {
this.clearFocusTime();
this.clearBlurTime();
+ this.clearComboboxTime();
if (this.dropdownContainer) {
document.body.removeChild(this.dropdownContainer);
this.dropdownContainer = null;
@@ -326,7 +339,7 @@ const Select = {
if (nextValue !== undefined) {
this.fireChange(nextValue);
}
- this.setOpenState(false, true);
+ this.setOpenState(false, { needFocus: true });
this.setInputValue('', false);
return;
}
@@ -378,14 +391,14 @@ const Select = {
},
onInputKeydown(event) {
- const props = this.$props;
- if (props.disabled) {
+ const { disabled, combobox, defaultActiveFirstOption } = this.$props;
+ if (disabled) {
return;
}
const state = this.$data;
const isRealOpen = this.getRealOpenState(state);
const keyCode = event.keyCode;
- if (isMultipleOrTags(props) && !event.target.value && keyCode === KeyCode.BACKSPACE) {
+ if (isMultipleOrTags(this.$props) && !event.target.value && keyCode === KeyCode.BACKSPACE) {
event.preventDefault();
const { _value: value } = state;
if (value.length) {
@@ -407,6 +420,12 @@ const Select = {
if (isRealOpen || !props.combobox) {
event.preventDefault();
}
+ // Hard close popup to avoid lock of non option in combobox mode
+ if (isRealOpen && combobox && defaultActiveFirstOption === false) {
+ this.comboboxTimer = setTimeout(() => {
+ this.setOpenState(false);
+ });
+ }
} else if (keyCode === KeyCode.ESC) {
if (state._open) {
this.setOpenState(false);
@@ -433,12 +452,14 @@ const Select = {
const props = this.$props;
const selectedValue = getValuePropValue(item);
const lastValue = value[value.length - 1];
- this.fireSelect(selectedValue);
+ let skipTrigger = false;
+
if (isMultipleOrTags(props)) {
if (findIndexInValueBySingleValue(value, selectedValue) !== -1) {
- return;
+ skipTrigger = true;
+ } else {
+ value = value.concat([selectedValue]);
}
- value = value.concat([selectedValue]);
} else {
if (
!isCombobox(props) &&
@@ -446,30 +467,40 @@ const Select = {
lastValue === selectedValue &&
selectedValue !== this.$data._backfillValue
) {
- this.setOpenState(false, true);
- return;
+ this.setOpenState(false, { needFocus: true, fireSearch: false });
+ skipTrigger = true;
+ } else {
+ value = [selectedValue];
+ this.setOpenState(false, { needFocus: true, fireSearch: false });
}
- value = [selectedValue];
- this.setOpenState(false, true);
}
- this.fireChange(value);
- const inputValue = isCombobox(props) ? getPropValue(item, props.optionLabelProp) : '';
+ if (!skipTrigger) {
+ this.fireChange(value);
+ }
+ if (!skipTrigger) {
+ this.fireSelect(selectedValue);
+ const inputValue = isCombobox(props) ? getPropValue(item, props.optionLabelProp) : '';
- if (props.autoClearSearchValue) {
- this.setInputValue(inputValue, false);
+ if (props.autoClearSearchValue) {
+ this.setInputValue(inputValue, false);
+ }
}
},
onMenuDeselect({ item, domEvent }) {
if (domEvent.type === 'keydown' && domEvent.keyCode === KeyCode.ENTER) {
- this.removeSelected(getValuePropValue(item));
+ const menuItemDomNode = item.$el;
+ // https://github.com/ant-design/ant-design/issues/20465#issuecomment-569033796
+ if (!isHidden(menuItemDomNode)) {
+ this.removeSelected(getValuePropValue(item));
+ }
return;
}
if (domEvent.type === 'click') {
this.removeSelected(getValuePropValue(item));
}
if (this.autoClearSearchValue) {
- this.setInputValue('', false);
+ this.setInputValue('');
}
},
@@ -478,7 +509,7 @@ const Select = {
e.preventDefault();
this.clearBlurTime();
if (!this.disabled) {
- this.setOpenState(!this.$data._open, !this.$data._open);
+ this.setOpenState(!this.$data._open, { needFocus: !this.$data._open });
}
},
@@ -505,7 +536,7 @@ const Select = {
if (value.length) {
this.fireChange([]);
}
- this.setOpenState(false, true);
+ this.setOpenState(false, { needFocus: true });
if (inputValue) {
this.setInputValue('');
}
@@ -527,9 +558,12 @@ const Select = {
}
let defaultLabel = value;
if (this.$props.labelInValue) {
- const label = getLabelFromPropsValue(this.$props.value, value);
- if (label !== undefined) {
- defaultLabel = label;
+ const valueLabel = getLabelFromPropsValue(this.$props.value, value);
+ const defaultValueLabel = getLabelFromPropsValue(this.$props.defaultValue, value);
+ if (valueLabel !== undefined) {
+ defaultLabel = valueLabel;
+ } else if (defaultValueLabel !== undefined) {
+ defaultLabel = defaultValueLabel;
}
}
const defaultInfo = {
@@ -734,7 +768,18 @@ const Select = {
return;
}
this.clearBlurTime();
- if (!isMultipleOrTagsOrCombobox(this.$props) && e.target === this.getInputDOMNode()) {
+
+ // In IE11, onOuterFocus will be trigger twice when focus input
+ // First one: e.target is div
+ // Second one: e.target is input
+ // other browser only trigger second one
+ // https://github.com/ant-design/ant-design/issues/15942
+ // Here we ignore the first one when e.target is div
+ const inputNode = this.getInputDOMNode();
+ if (inputNode && e.target === this.rootRef) {
+ return;
+ }
+ if (!isMultipleOrTagsOrCombobox(this.$props) && e.target === inputNode) {
return;
}
if (this._focused) {
@@ -837,8 +882,9 @@ const Select = {
}
},
- setOpenState(open, needFocus) {
+ setOpenState(open, config = {}) {
const { $props: props, $data: state } = this;
+ const { needFocus, fireSearch } = config;
if (state._open === open) {
this.maybeFocus(open, !!needFocus);
return;
@@ -850,7 +896,7 @@ const Select = {
};
// clear search input value when open is false in singleMode.
if (!open && isSingleMode(props) && props.showSearch) {
- this.setInputValue('', false);
+ this.setInputValue('', fireSearch);
}
if (!open) {
this.maybeFocus(open, !!needFocus);
@@ -1002,6 +1048,13 @@ const Select = {
}
},
+ clearComboboxTime() {
+ if (this.comboboxTimer) {
+ clearTimeout(this.comboboxTimer);
+ this.comboboxTimer = null;
+ }
+ },
+
updateFocusClassName() {
const { rootRef, prefixCls } = this;
// avoid setState and its side effect
@@ -1130,30 +1183,18 @@ const Select = {
options.push(menuItem);
menuItems.push(menuItem);
});
- if (inputValue) {
- const notFindInputItem = menuItems.every(option => {
- // this.filterOption return true has two meaning,
- // 1, some one exists after filtering
- // 2, filterOption is set to false
- // condition 2 does not mean the option has same value with inputValue
- const filterFn = () => getValuePropValue(option) === inputValue;
- if (filterOption !== false) {
- return !this._filterOption(inputValue, option, filterFn);
- }
- return !filterFn();
- });
- if (notFindInputItem) {
- const p = {
- attrs: UNSELECTABLE_ATTRIBUTE,
- key: inputValue,
- props: {
- value: inputValue,
- role: 'option',
- },
- style: UNSELECTABLE_STYLE,
- };
- options.unshift();
- }
+ // ref: https://github.com/ant-design/ant-design/issues/14090
+ if (inputValue && menuItems.every(option => getValuePropValue(option) !== inputValue)) {
+ const p = {
+ attrs: UNSELECTABLE_ATTRIBUTE,
+ key: inputValue,
+ props: {
+ value: inputValue,
+ role: 'option',
+ },
+ style: UNSELECTABLE_STYLE,
+ };
+ options.unshift();
}
}
diff --git a/components/vc-select/SelectTrigger.jsx b/components/vc-select/SelectTrigger.jsx
index 7dfe3d2d9..ba8e906a3 100644
--- a/components/vc-select/SelectTrigger.jsx
+++ b/components/vc-select/SelectTrigger.jsx
@@ -1,4 +1,5 @@
import classnames from 'classnames';
+import raf from 'raf';
import Trigger from '../vc-trigger';
import PropTypes from '../_util/vue-types';
import DropdownMenu from './DropdownMenu';
@@ -65,6 +66,7 @@ export default {
};
},
created() {
+ this.rafInstance = null;
this.saveDropdownMenuRef = saveRef(this, 'dropdownMenuRef');
this.saveTriggerRef = saveRef(this, 'triggerRef');
},
@@ -80,14 +82,24 @@ export default {
this.setDropdownWidth();
});
},
+ beforeDestroy() {
+ this.cancelRafInstance();
+ },
methods: {
setDropdownWidth() {
- const width = this.$el.offsetWidth;
- if (width !== this.dropdownWidth) {
- this.setState({ dropdownWidth: width });
+ this.cancelRafInstance();
+ this.rafInstance = raf(() => {
+ const width = this.$el.offsetWidth;
+ if (width !== this.dropdownWidth) {
+ this.setState({ dropdownWidth: width });
+ }
+ });
+ },
+ cancelRafInstance() {
+ if (this.rafInstance) {
+ raf.cancel(this.rafInstance);
}
},
-
getInnerMenu() {
return this.dropdownMenuRef && this.dropdownMenuRef.$refs.menuRef;
},
diff --git a/components/vc-select/index.js b/components/vc-select/index.js
index 0fd5fb7b1..51b6f86a7 100644
--- a/components/vc-select/index.js
+++ b/components/vc-select/index.js
@@ -1,4 +1,4 @@
-// based on vc-select 8.9.0
+// based on vc-select 9.2.2
import ProxySelect, { Select } from './Select';
import Option from './Option';
import { SelectPropTypes } from './PropTypes';