update input

This commit is contained in:
wangxueliang 2019-04-07 17:19:18 +08:00
parent ca7a4becbc
commit 22b051f0a5
15 changed files with 376 additions and 98 deletions

View File

@ -1,9 +1,10 @@
import { filterEmpty } from '../_util/props-util';
import { ConfigConsumerProps } from '../config-provider';
export default {
name: 'AInputGroup',
props: {
prefixCls: {
default: 'ant-input-group',
type: String,
},
size: {
@ -13,9 +14,15 @@ export default {
},
compact: Boolean,
},
inject: {
configProvider: { default: () => ({}) },
},
computed: {
classes() {
const { prefixCls, size, compact = false } = this;
const { prefixCls: customizePrefixCls, size, compact = false } = this;
const getPrefixCls = this.configProvider.getPrefixCls || ConfigConsumerProps.getPrefixCls;
const prefixCls = getPrefixCls('input-group', customizePrefixCls);
return {
[`${prefixCls}`]: true,
[`${prefixCls}-lg`]: size === 'large',

View File

@ -4,6 +4,10 @@ import omit from 'omit.js';
import inputProps from './inputProps';
import { hasProp, getComponentFromProp } from '../_util/props-util';
import { isIE, isIE9 } from '../_util/env';
import { ConfigConsumerProps } from '../config-provider';
import Password from './Password';
import Icon from '../icon';
import warning from '../_util/warning';
function noop() {}
@ -14,6 +18,10 @@ function fixControlledValue(value) {
return value;
}
function hasPrefixSuffix(props) {
return 'prefix' in props || props.suffix || props.allowClear;
}
export default {
name: 'AInput',
inheritAttrs: false,
@ -24,6 +32,9 @@ export default {
props: {
...inputProps,
},
inject: {
configProvider: { default: () => ({}) },
},
data() {
const { value, defaultValue } = this.$props;
return {
@ -49,22 +60,6 @@ export default {
}
this.$emit('keydown', e);
},
handleChange(e) {
// https://github.com/vueComponent/ant-design-vue/issues/92
if (isIE && !isIE9 && this.stateValue === e.target.value) {
return;
}
if (!hasProp(this, 'value')) {
this.stateValue = e.target.value;
} else {
this.$forceUpdate();
}
if (!e.target.composing) {
this.$emit('change.value', e.target.value);
}
this.$emit('change', e);
this.$emit('input', e);
},
focus() {
this.$refs.input.focus();
@ -77,8 +72,8 @@ export default {
this.$refs.input.select();
},
getInputClassName() {
const { prefixCls, size, disabled } = this.$props;
getInputClassName(prefixCls) {
const { size, disabled } = this.$props;
return {
[`${prefixCls}`]: true,
[`${prefixCls}-sm`]: size === 'small',
@ -86,7 +81,64 @@ export default {
[`${prefixCls}-disabled`]: disabled,
};
},
renderLabeledInput(children) {
setValue(value, e) {
// https://github.com/vueComponent/ant-design-vue/issues/92
if (isIE && !isIE9 && this.stateValue === value) {
return;
}
if (!hasProp(this, 'value')) {
this.stateValue = value;
} else {
this.$forceUpdate();
}
if (!e.target.composing) {
this.$emit('change.value', value);
}
this.$emit('change', e);
this.$emit('input', e);
},
handleReset(e) {
this.setValue('', e);
},
handleChange(e) {
this.setValue(e.target.value, e);
},
renderClearIcon(prefixCls) {
const { allowClear } = this.$props;
const { stateValue } = this;
if (!allowClear || stateValue === undefined || stateValue === null || stateValue === '') {
return null;
}
return (
<Icon
type="close-circle"
theme="filled"
onClick={this.handleReset}
class={`${prefixCls}-clear-icon`}
role="button"
/>
);
},
renderSuffix(prefixCls) {
const { allowClear } = this.$props;
let suffix = getComponentFromProp(this, 'suffix');
if (suffix || allowClear) {
return (
<span class={`${prefixCls}-suffix`}>
{this.renderClearIcon(prefixCls)}
{suffix}
</span>
);
}
return null;
},
renderLabeledInput(prefixCls, children) {
const props = this.$props;
let addonAfter = getComponentFromProp(this, 'addonAfter');
let addonBefore = getComponentFromProp(this, 'addonBefore');
@ -95,24 +147,24 @@ export default {
return children;
}
const wrapperClassName = `${props.prefixCls}-group`;
const wrapperClassName = `${prefixCls}-group`;
const addonClassName = `${wrapperClassName}-addon`;
addonBefore = addonBefore ? <span class={addonClassName}>{addonBefore}</span> : null;
addonAfter = addonAfter ? <span class={addonClassName}>{addonAfter}</span> : null;
const className = {
[`${props.prefixCls}-wrapper`]: true,
const mergedWrapperClassName = {
[`${prefixCls}-wrapper`]: true,
[wrapperClassName]: addonBefore || addonAfter,
};
const groupClassName = classNames(`${props.prefixCls}-group-wrapper`, {
[`${props.prefixCls}-group-wrapper-sm`]: props.size === 'small',
[`${props.prefixCls}-group-wrapper-lg`]: props.size === 'large',
const mergedGroupClassName = classNames(`${prefixCls}-group-wrapper`, {
[`${prefixCls}-group-wrapper-sm`]: props.size === 'small',
[`${prefixCls}-group-wrapper-lg`]: props.size === 'large',
});
return (
<span class={groupClassName}>
<span class={className}>
<span class={mergedGroupClassName}>
<span class={mergedWrapperClassName}>
{addonBefore}
{children}
{addonAfter}
@ -120,17 +172,15 @@ export default {
</span>
);
},
renderLabeledIcon(children) {
const { prefixCls, size } = this.$props;
let prefix = getComponentFromProp(this, 'prefix');
let suffix = getComponentFromProp(this, 'suffix');
if (!prefix && !suffix) {
renderLabeledIcon(prefixCls, children) {
const { size } = this.$props;
let suffix = this.renderSuffix(prefixCls);
if (!hasPrefixSuffix(this.$props)) {
return children;
}
let prefix = getComponentFromProp(this, 'prefix');
prefix = prefix ? <span class={`${prefixCls}-prefix`}>{prefix}</span> : null;
suffix = suffix ? <span class={`${prefixCls}-suffix`}>{suffix}</span> : null;
const affixWrapperCls = classNames(`${prefixCls}-affix-wrapper`, {
[`${prefixCls}-affix-wrapper-sm`]: size === 'small',
[`${prefixCls}-affix-wrapper-lg`]: size === 'large',
@ -144,7 +194,7 @@ export default {
);
},
renderInput() {
renderInput(prefixCls) {
const otherProps = omit(this.$props, [
'prefixCls',
'addonBefore',
@ -166,13 +216,13 @@ export default {
input: handleChange,
change: noop,
},
class: getInputClassName(),
class: getInputClassName(prefixCls),
ref: 'input',
};
if ($listeners['change.value']) {
inputProps.directives = [{ name: 'ant-input' }];
}
return this.renderLabeledIcon(<input {...inputProps} />);
return this.renderLabeledIcon(prefixCls,<input {...inputProps} />);
},
},
render() {
@ -194,6 +244,9 @@ export default {
};
return <TextArea {...textareaProps} ref="input" />;
}
return this.renderLabeledInput(this.renderInput());
const { prefixCls: customizePrefixCls } = this.$props;
const getPrefixCls = this.configProvider.getPrefixCls || ConfigConsumerProps.getPrefixCls;
const prefixCls = getPrefixCls('input', customizePrefixCls);
return this.renderLabeledInput(prefixCls, this.renderInput(prefixCls));
},
};

View File

@ -0,0 +1,86 @@
import classNames from 'classnames';
import Input from './Input';
import Icon from '../icon';
import inputProps from './inputProps';
import Button from '../button';
import { cloneElement } from '../_util/vnode';
import { getOptionProps, getComponentFromProp, isValidElement } from '../_util/props-util';
import PropTypes from '../_util/vue-types';
import BaseMixin from '../_util/BaseMixin';
const ActionMap = {
click: 'click',
hover: 'mouseover',
};
export default {
name: 'AInputPassword',
model: {
prop: 'value',
event: 'change.value',
},
props: {
...inputProps,
prefixCls: PropTypes.string.def('ant-input-password'),
inputPrefixCls: PropTypes.string.def('ant-input'),
action: PropTypes.string.def('click'),
visibilityToggle: PropTypes.bool.def(true),
},
data() {
return {
visible: false,
};
},
mixins: [BaseMixin],
methods: {
onChange(e) {
this.setState({
visible: !this.visible,
});
},
getIcon() {
const { prefixCls, action } = this.$props;
const iconTrigger = ActionMap[action] || '';
const iconProps = {
props: {
type: this.visible ? 'eye' : 'eye-invisible',
},
on: {
[iconTrigger]: this.onChange,
onMouseDown: (e) => {
// Prevent focused state lost
// https://github.com/ant-design/ant-design/issues/15173
e.preventDefault();
},
},
class: `${prefixCls}-icon`,
key: 'passwordIcon',
};
return <Icon {...iconProps} />;
},
},
render() {
const {
prefixCls,
inputPrefixCls,
size,
suffix,
visibilityToggle,
...restProps
} = this.$props;
const suffixIcon = visibilityToggle && this.getIcon();
const inputClassName = classNames(prefixCls, {
[`${prefixCls}-${size}`]: !!size,
});
return (
<Input
{...restProps}
type={this.visible ? 'text' : 'password'}
size={size}
class={inputClassName}
prefixCls={inputPrefixCls}
suffix={suffixIcon}
/>
);
},
};

View File

@ -6,6 +6,7 @@ import Button from '../button';
import { cloneElement } from '../_util/vnode';
import { getOptionProps, getComponentFromProp, isValidElement } from '../_util/props-util';
import PropTypes from '../_util/vue-types';
import { ConfigConsumerProps } from '../config-provider';
export default {
name: 'AInputSearch',
@ -25,6 +26,9 @@ export default {
},
enterButton: PropTypes.oneOfType([PropTypes.bool, PropTypes.string, PropTypes.object]),
},
inject: {
configProvider: { default: () => ({}) },
},
methods: {
onSearch(e) {
this.$emit('search', this.$refs.input.stateValue, e);
@ -37,62 +41,96 @@ export default {
blur() {
this.$refs.input.blur();
},
getButtonOrIcon() {
const { prefixCls, size, disabled } = this;
renderSuffix(prefixCls) {
const suffix = getComponentFromProp(this, 'suffix');
const enterButton = getComponentFromProp(this, 'enterButton');
if (enterButton) return suffix;
const node = (
<Icon
class={`${prefixCls}-icon`}
type="search"
key="searchIcon"
onClick={this.onSearch}
/>
);
if (suffix) {
let cloneSuffix = suffix;
if (isValidElement(cloneSuffix) && !cloneSuffix.key) {
cloneSuffix = cloneElement(cloneSuffix, {
key: 'originSuffix',
});
}
return [cloneSuffix, node];
}
return node;
},
renderAddonAfter(prefixCls) {
const { size, disabled } = this;
const enterButton = getComponentFromProp(this, 'enterButton');
const addonAfter = getComponentFromProp(this, 'addonAfter');
if(!enterButton) return addonAfter;
const btnClassName = `${prefixCls}-button`;
const enterButtonAsElement = Array.isArray(enterButton) ? enterButton[0] : enterButton;
let node;
if (!enterButton) {
node = <Icon class={`${prefixCls}-icon`} type="search" key="searchIcon" />;
} else if (
let button;
if (
enterButtonAsElement.tag === 'button' ||
(enterButtonAsElement.componentOptions &&
enterButtonAsElement.componentOptions.Ctor.extendOptions.__ANT_BUTTON)
) {
node = cloneElement(enterButtonAsElement, {
class: `${prefixCls}-button`,
button = cloneElement(enterButtonAsElement, {
class: btnClassName,
props: { size },
on: {
click: this.onSearch,
},
});
} else {
node = (
button = (
<Button
class={`${prefixCls}-button`}
class={btnClassName}
type="primary"
size={size}
disabled={disabled}
key="enterButton"
onClick={this.onSearch}
>
{enterButton === true ? <Icon type="search" /> : enterButton}
</Button>
);
}
return cloneElement(node, {
on: {
click: this.onSearch,
},
});
if (addonAfter) {
return [button, addonAfter];
}
return button;
},
},
render() {
const { prefixCls, inputPrefixCls, size, ...others } = getOptionProps(this);
const suffix = getComponentFromProp(this, 'suffix');
const {
prefixCls: customizePrefixCls,
inputPrefixCls: customizeInputPrefixCls,
size,
...others
} = getOptionProps(this);
const getPrefixCls = this.configProvider.getPrefixCls || ConfigConsumerProps.getPrefixCls;
const prefixCls = getPrefixCls('input-search', customizePrefixCls);
const inputPrefixCls = getPrefixCls('input', customizeInputPrefixCls);
const enterButton = getComponentFromProp(this, 'enterButton');
const addonAfter = getComponentFromProp(this, 'addonAfter');
const addonBefore = getComponentFromProp(this, 'addonBefore');
const buttonOrIcon = this.getButtonOrIcon();
let searchSuffix = suffix ? [suffix, buttonOrIcon] : buttonOrIcon;
if (Array.isArray(searchSuffix)) {
searchSuffix = searchSuffix.map((item, index) => {
if (!isValidElement(item) || item.key) {
return item;
}
return cloneElement(item, { key: index });
let inputClassName;
if(enterButton) {
inputClassName = classNames(prefixCls, {
[`${prefixCls}-enter-button`]: !!enterButton,
[`${prefixCls}-${size}`]: !!size,
});
} else {
inputClassName = prefixCls;
}
const inputClassName = classNames(prefixCls, {
[`${prefixCls}-enter-button`]: !!enterButton,
[`${prefixCls}-${size}`]: !!size,
});
const on = { ...this.$listeners };
delete on.search;
const inputProps = {
@ -100,16 +138,18 @@ export default {
...others,
prefixCls: inputPrefixCls,
size,
suffix: searchSuffix,
addonAfter,
suffix: this.renderSuffix(prefixCls),
addonAfter: this.renderAddonAfter(prefixCls),
addonBefore,
},
attrs: this.$attrs,
class: inputClassName,
ref: 'input',
on: {
pressEnter: this.onSearch,
...on,
},
};
return <Input {...inputProps} class={inputClassName} ref="input" />;
return <Input {...inputProps} />;
},
};

View File

@ -1,8 +1,10 @@
import classNames from 'classnames';
import omit from 'omit.js';
import ResizeObserver from 'resize-observer-polyfill';
import inputProps from './inputProps';
import calculateNodeHeight from './calculateNodeHeight';
import hasProp from '../_util/props-util';
import { ConfigConsumerProps } from '../config-provider';
function onNextFrame(cb) {
if (window.requestAnimationFrame) {
@ -36,6 +38,9 @@ export default {
...inputProps,
autosize: [Object, Boolean],
},
inject: {
configProvider: { default: () => ({}) },
},
data() {
const { value, defaultValue } = this.$props;
return {
@ -105,20 +110,11 @@ export default {
if (!autosize || !this.$refs.textArea) {
return;
}
const minRows = autosize ? autosize.minRows : null;
const maxRows = autosize ? autosize.maxRows : null;
const { minRows, maxRows } = autosize;
const textareaStyles = calculateNodeHeight(this.$refs.textArea, false, minRows, maxRows);
this.textareaStyles = textareaStyles;
},
getTextAreaClassName() {
const { prefixCls, disabled } = this.$props;
return {
[prefixCls]: true,
[`${prefixCls}-disabled`]: disabled,
};
},
handleTextareaChange(e) {
if (!hasProp(this, 'value')) {
this.stateValue = e.target.value;
@ -144,12 +140,13 @@ export default {
render() {
const {
stateValue,
getTextAreaClassName,
handleKeyDown,
handleTextareaChange,
textareaStyles,
$attrs,
$listeners,
prefixCls: customizePrefixCls,
disabled,
} = this;
const otherProps = omit(this.$props, [
'prefixCls',
@ -158,6 +155,13 @@ export default {
'value',
'defaultValue',
]);
const getPrefixCls = this.configProvider.getPrefixCls || ConfigConsumerProps.getPrefixCls;
const prefixCls = getPrefixCls('input', customizePrefixCls);
const cls = classNames(prefixCls, {
[`${prefixCls}-disabled`]: disabled,
});
const textareaProps = {
attrs: { ...otherProps, ...$attrs },
on: {
@ -174,7 +178,7 @@ export default {
<textarea
{...textareaProps}
value={stateValue}
class={getTextAreaClassName()}
class={cls}
style={textareaStyles}
ref="textArea"
/>

View File

@ -24,6 +24,7 @@ const SIZING_STYLE = [
'font-family',
'font-weight',
'font-size',
'font-variant',
'text-rendering',
'text-transform',
'width',
@ -37,7 +38,7 @@ const SIZING_STYLE = [
const computedStyleCache = {};
let hiddenTextarea;
function calculateNodeStyling(node, useCache = false) {
export function calculateNodeStyling(node, useCache = false) {
const nodeRef =
node.getAttribute('id') || node.getAttribute('data-reactid') || node.getAttribute('name');

View File

@ -0,0 +1,24 @@
<cn>
#### 带移除图标
带移除图标的输入框,点击图标删除所有内容。
</cn>
<us>
#### With clear icon
Input type of password.
</us>
```html
<template>
<a-input placeholder="input with clear icon" allowClear @change="onChange" />
</template>
<script>
export default {
methods: {
onChange(e) {
console.log(e);
}
}
}
</script>
```

View File

@ -14,12 +14,14 @@ Note: You don't need `Col` to control the width in the `compact` mode.
<template>
<div>
<a-input-group size="large">
<a-col :span="5">
<a-input defaultValue="0571" />
</a-col>
<a-col :span="8">
<a-input defaultValue="26888888" />
</a-col>
<a-row :gutter="8">
<a-col :span="5">
<a-input defaultValue="0571" />
</a-col>
<a-col :span="8">
<a-input defaultValue="26888888" />
</a-col>
</a-row>
</a-input-group>
<br />
<a-input-group compact>
@ -45,7 +47,7 @@ Note: You don't need `Col` to control the width in the `compact` mode.
<br />
<a-input-group compact>
<a-input style="width: 50%" defaultValue="input content" />
<a-date-picker />
<a-date-picker style="width: 50%" />
</a-input-group>
<br />
<a-input-group compact>

View File

@ -6,6 +6,8 @@ import SearchInput from './search-input';
import Size from './size';
import Group from './group';
import TextArea from './textarea';
import AllowClear from './allowClear';
import PasswordInput from './password-input';
import Addon from './addon';
import Tooltip from './tooltip';
import CN from '../index.zh-CN.md';
@ -22,7 +24,7 @@ const md = {
Keyboard and mouse can be used for providing or changing data.
## When To Use
- A user input in a form field is needed.
- A search input is required.
- A search input is required.
## Examples `,
};
export default {
@ -43,6 +45,8 @@ export default {
<TextArea />
<Addon />
<Tooltip />
<AllowClear />
<PasswordInput />
<api>
<CN slot='cn' />
<US/>

View File

@ -0,0 +1,15 @@
<cn>
#### 密码框
密码框,版本 1.4.0 中新增。
</cn>
<us>
#### Password box
Input type of password and added in 1.4.0.
</us>
```html
<template>
<a-input-password placeholder="input password" />
</template>
```

View File

@ -11,7 +11,7 @@ For multi-line input.
```html
<template>
<div>
<a-button @click="() => this.autoResize = !autoResize">
<a-button style="margin-bottom: 16px" @click="() => this.autoResize = !autoResize">
Auto Resize: {String(autoResize)}
</a-button>
<a-textarea :rows="4" :autosize="autoResize" :defaultValue="defaultValue" />

View File

@ -15,6 +15,7 @@
| suffix | The suffix icon for the Input. | string\|slot | |
| type | The type of input, see: [MDN](https://developer.mozilla.org/docs/Web/HTML/Element/input#Form_%3Cinput%3E_types)(use `Input.TextArea` instead of `type="textarea"`) | string | `text` |
| value(v-model) | The input content value | string | |
| allowClear | allow to remove input content with clear icon | boolean | |
### Input Events
| Events Name | Description | Arguments |
@ -45,7 +46,7 @@ The rest of the props of `Input.TextArea` are the same as the original [textarea
| Property | Description | Type | Default |
| -------- | ----------- | ---- | ------- |
| enterButton | to show an enter button after input | boolean\|slot | false |
| enterButton | to show an enter button after input. This prop is conflict with addon. | boolean\|slot | false |
### Input.Search Events
| Events Name | Description | Arguments |
@ -67,3 +68,21 @@ Supports all props of `Input`.
<a-input />
</a-input-group>
````
#### Input.Password
| Property | Description | Type | Default |
| --- | --- | --- | --- |
| visibilityToggle | Whether show toggle button | boolean | true |
## FAQ
### Why Input lose focus when change `prefix/suffix`
When Input dynamic add or remove `prefix/suffix` will make Vue recreate the dom structure and new input will be not focused.
You can set an empty `<span />` element to keep the dom structure:
```jsx
const suffix = condition ? <Icon type="smile" /> : <span />;
<Input suffix={suffix} />
```

View File

@ -3,6 +3,7 @@ import Input from './Input';
import Group from './Group';
import Search from './Search';
import TextArea from './TextArea';
import Password from './Password';
import antInputDirective from '../_util/antInputDirective';
Vue.use(antInputDirective);
@ -10,6 +11,7 @@ Vue.use(antInputDirective);
Input.Group = Group;
Input.Search = Search;
Input.TextArea = TextArea;
Input.Password = Password;
/* istanbul ignore next */
Input.install = function(Vue) {
@ -17,6 +19,7 @@ Input.install = function(Vue) {
Vue.component(Input.Group.name, Input.Group);
Vue.component(Input.Search.name, Input.Search);
Vue.component(Input.TextArea.name, Input.TextArea);
Vue.component(Input.Password.name, Input.Password);
};
export default Input;

View File

@ -15,6 +15,7 @@
| suffix | 带有后缀图标的 input | string\|slot | |
| type | 声明 input 类型,同原生 input 标签的 type 属性,见:[MDN](https://developer.mozilla.org/zh-CN/docs/Web/HTML/Element/input#属性)(请直接使用 `Input.TextArea` 代替 `type="textarea"`)。 | string | `text` |
| value(v-model) | 输入框内容 | string | |
| allowClear | 可以点击清除图标删除内容 | boolean | |
### Input 事件
| 事件名称 | 说明 | 回调参数 |
@ -44,7 +45,7 @@
| 参数 | 说明 | 类型 | 默认值 |
| --- | --- | --- | --- |
| enterButton | 是否有确认按钮,可设为按钮文字 | boolean\|slot | false |
| enterButton | 是否有确认按钮,可设为按钮文字。该属性会与 addon 冲突。 | boolean\|slot | false |
### Input.Search 事件
| 事件名称 | 说明 | 回调参数 |
@ -66,3 +67,22 @@
<a-input />
</a-input-group>
````
#### Input.Password
| 参数 | 说明 | 类型 | 默认值 |
| --- | --- | --- | --- |
| visibilityToggle | 是否显示切换按钮 | boolean | true |
## FAQ
### 为什么我动态改变 `prefix/suffix`Input 会失去焦点?
当 Input 动态添加或者删除 `prefix/suffix`Vue 会重新创建 DOM 结构而新的 input 是没有焦点的。
你可以预设一个空的 `<span />` 来保持 DOM 结构不变:
```jsx
const suffix = condition ? <Icon type="smile" /> : <span />;
<Input suffix={suffix} />
```

View File

@ -1,7 +1,6 @@
import PropTypes from '../_util/vue-types';
export default {
prefixCls: {
default: 'ant-input',
type: String,
},
defaultValue: [String, Number],
@ -34,4 +33,5 @@ export default {
suffix: PropTypes.any,
spellCheck: Boolean,
autoFocus: Boolean,
allowClear: Boolean,
};