diff --git a/build/config.js b/build/config.js
index 2d7f6e9dc..a75a866b4 100644
--- a/build/config.js
+++ b/build/config.js
@@ -1,5 +1,5 @@
module.exports = {
dev: {
- componentName: 'slider', // dev components
+ componentName: 'input', // dev components
},
};
diff --git a/components/affix/index.jsx b/components/affix/index.jsx
index 7dd2672c2..d67679fc9 100644
--- a/components/affix/index.jsx
+++ b/components/affix/index.jsx
@@ -1,9 +1,8 @@
import PropTypes from '../_util/vue-types';
-import addEventListener from '../vc-util/Dom/addEventListener';
import classNames from 'classnames';
import shallowequal from 'shallowequal';
import omit from 'omit.js';
-import getScroll from '../_util/getScroll';
+import ResizeObserver from '../vc-resize-observer';
import BaseMixin from '../_util/BaseMixin';
import throttleByAnimationFrame from '../_util/throttleByAnimationFrame';
import { ConfigConsumerProps } from '../config-provider';
@@ -242,11 +241,17 @@ const Affix = {
attrs: omit($props, ['prefixCls', 'offsetTop', 'offsetBottom', 'target']),
};
return (
-
-
- {$slots.default}
+
{
+ this.updatePosition();
+ }}
+ >
+
-
+
);
},
};
diff --git a/components/input/ClearableLabeledInput.jsx b/components/input/ClearableLabeledInput.jsx
new file mode 100644
index 000000000..9237f182b
--- /dev/null
+++ b/components/input/ClearableLabeledInput.jsx
@@ -0,0 +1,173 @@
+import classNames from 'classnames';
+import Icon from '../icon';
+import { getInputClassName } from './Input';
+import PropTypes from '../_util/vue-types';
+import { cloneElement } from '../_util/vnode';
+import { getComponentFromProp } from '../_util/props-util';
+
+export function hasPrefixSuffix(instance) {
+ return !!(
+ getComponentFromProp(instance, 'prefix') ||
+ getComponentFromProp(instance, 'suffix') ||
+ instance.$props.allowClear
+ );
+}
+
+const ClearableInputType = ['text', 'input'];
+
+const ClearableLabeledInput = {
+ props: {
+ prefixCls: PropTypes.string,
+ inputType: PropTypes.oneOf(ClearableInputType),
+ value: PropTypes.any,
+ defaultValue: PropTypes.any,
+ allowClear: PropTypes.bool,
+ element: PropTypes.any,
+ handleReset: PropTypes.func,
+ disabled: PropTypes.bool,
+ size: PropTypes.oneOf(['small', 'large', 'default']),
+ suffix: PropTypes.any,
+ prefix: PropTypes.any,
+ addonBefore: PropTypes.any,
+ addonAfter: PropTypes.any,
+ className: PropTypes.string,
+ },
+ methods: {
+ renderClearIcon(prefixCls) {
+ const { allowClear, value, disabled, inputType, handleReset } = this.$props;
+ if (!allowClear || disabled || value === undefined || value === null || value === '') {
+ return null;
+ }
+ const className =
+ inputType === ClearableInputType[0]
+ ? `${prefixCls}-textarea-clear-icon`
+ : `${prefixCls}-clear-icon`;
+ return (
+
+ );
+ },
+
+ renderSuffix(prefixCls) {
+ const { suffix, allowClear } = this.$props;
+ if (suffix || allowClear) {
+ return (
+
+ {this.renderClearIcon(prefixCls)}
+ {suffix}
+
+ );
+ }
+ return null;
+ },
+
+ renderLabeledIcon(prefixCls, element) {
+ const props = this.$props;
+ const suffix = this.renderSuffix(prefixCls);
+ if (!hasPrefixSuffix(this)) {
+ return cloneElement(element, {
+ props: { value: props.value },
+ });
+ }
+
+ const prefix = props.prefix ? (
+
{props.prefix}
+ ) : null;
+
+ const affixWrapperCls = classNames(props.className, `${prefixCls}-affix-wrapper`, {
+ [`${prefixCls}-affix-wrapper-sm`]: props.size === 'small',
+ [`${prefixCls}-affix-wrapper-lg`]: props.size === 'large',
+ [`${prefixCls}-affix-wrapper-input-with-clear-btn`]:
+ props.suffix && props.allowClear && this.$props.value,
+ });
+ return (
+
+ {prefix}
+ {cloneElement(element, {
+ style: null,
+ props: { value: props.value },
+ class: getInputClassName(prefixCls, props.size, props.disabled),
+ })}
+ {suffix}
+
+ );
+ },
+
+ renderInputWithLabel(prefixCls, labeledElement) {
+ const { addonBefore, addonAfter, style, size, className } = this.$props;
+ // Not wrap when there is not addons
+ if (!addonBefore && !addonAfter) {
+ return labeledElement;
+ }
+
+ const wrapperClassName = `${prefixCls}-group`;
+ const addonClassName = `${wrapperClassName}-addon`;
+ const addonBeforeNode = addonBefore ? (
+
{addonBefore}
+ ) : null;
+ const addonAfterNode = addonAfter ?
{addonAfter} : null;
+
+ const mergedWrapperClassName = classNames(`${prefixCls}-wrapper`, {
+ [wrapperClassName]: addonBefore || addonAfter,
+ });
+
+ const mergedGroupClassName = classNames(className, `${prefixCls}-group-wrapper`, {
+ [`${prefixCls}-group-wrapper-sm`]: size === 'small',
+ [`${prefixCls}-group-wrapper-lg`]: size === 'large',
+ });
+
+ // Need another wrapper for changing display:table to display:inline-block
+ // and put style prop in wrapper
+ return (
+
+
+ {addonBeforeNode}
+ {cloneElement(labeledElement, { style: null })}
+ {addonAfterNode}
+
+
+ );
+ },
+
+ renderTextAreaWithClearIcon(prefixCls, element) {
+ const { value, allowClear, className, style } = this.$props;
+ if (!allowClear) {
+ return cloneElement(element, {
+ props: { value },
+ });
+ }
+ const affixWrapperCls = classNames(
+ className,
+ `${prefixCls}-affix-wrapper`,
+ `${prefixCls}-affix-wrapper-textarea-with-clear-btn`,
+ );
+ return (
+
+ {cloneElement(element, {
+ style: null,
+ props: { value },
+ })}
+ {this.renderClearIcon(prefixCls)}
+
+ );
+ },
+
+ renderClearableLabeledInput() {
+ const { prefixCls, inputType, element } = this.$props;
+ if (inputType === ClearableInputType[0]) {
+ return this.renderTextAreaWithClearIcon(prefixCls, element);
+ }
+ return this.renderInputWithLabel(prefixCls, this.renderLabeledIcon(prefixCls, element));
+ },
+ },
+ render() {
+ return this.renderClearableLabeledInput();
+ },
+};
+
+export default ClearableLabeledInput;
diff --git a/components/input/Input.jsx b/components/input/Input.jsx
index 1e3a352eb..33aed8ec9 100644
--- a/components/input/Input.jsx
+++ b/components/input/Input.jsx
@@ -2,25 +2,45 @@ import classNames from 'classnames';
import TextArea from './TextArea';
import omit from 'omit.js';
import inputProps from './inputProps';
-import { hasProp, getComponentFromProp, getListeners } from '../_util/props-util';
+import { hasProp, getComponentFromProp, getListeners, getOptionProps } from '../_util/props-util';
import { ConfigConsumerProps } from '../config-provider';
-import Icon from '../icon';
+import ClearableLabeledInput from './ClearableLabeledInput';
function noop() {}
-function fixControlledValue(value) {
+export function fixControlledValue(value) {
if (typeof value === 'undefined' || value === null) {
return '';
}
return value;
}
-function hasPrefixSuffix(instance) {
- return !!(
- getComponentFromProp(instance, 'prefix') ||
- getComponentFromProp(instance, 'suffix') ||
- instance.$props.allowClear
- );
+export function resolveOnChange(target, e, onChange) {
+ if (onChange) {
+ let event = e;
+ if (e.type === 'click') {
+ // click clear icon
+ event = { ...e };
+ event.target = target;
+ event.currentTarget = target;
+ const originalInputValue = target.value;
+ // change target ref value cause e.target.value should be '' when clear input
+ target.value = '';
+ onChange(event);
+ // reset target ref value
+ target.value = originalInputValue;
+ return;
+ }
+ onChange(event);
+ }
+}
+
+export function getInputClassName(prefixCls, size, disabled) {
+ return classNames(prefixCls, {
+ [`${prefixCls}-sm`]: size === 'small',
+ [`${prefixCls}-lg`]: size === 'large',
+ [`${prefixCls}-disabled`]: disabled,
+ });
}
export default {
@@ -37,9 +57,10 @@ export default {
configProvider: { default: () => ConfigConsumerProps },
},
data() {
- const { value = '', defaultValue = '' } = this.$props;
+ const props = this.$props;
+ const value = typeof props.value === 'undefined' ? props.defaultValue : props.value;
return {
- stateValue: !hasProp(this, 'value') ? defaultValue : value,
+ stateValue: value,
};
},
watch: {
@@ -52,16 +73,15 @@ export default {
if (this.autoFocus) {
this.focus();
}
+ this.clearPasswordValueAttribute();
});
},
+ beforeDestroy() {
+ if (this.removePasswordTimeout) {
+ clearTimeout(this.removePasswordTimeout);
+ }
+ },
methods: {
- handleKeyDown(e) {
- if (e.keyCode === 13) {
- this.$emit('pressEnter', e);
- }
- this.$emit('keydown', e);
- },
-
focus() {
this.$refs.input.focus();
},
@@ -73,155 +93,30 @@ export default {
this.$refs.input.select();
},
- getInputClassName(prefixCls) {
- const { size, disabled } = this.$props;
- return {
- [`${prefixCls}`]: true,
- [`${prefixCls}-sm`]: size === 'small',
- [`${prefixCls}-lg`]: size === 'large',
- [`${prefixCls}-disabled`]: disabled,
- };
- },
-
- setValue(value, e) {
+ setValue(value, callback) {
if (this.stateValue === value) {
return;
}
if (!hasProp(this, 'value')) {
this.stateValue = value;
+ this.$nextTick(() => {
+ callback && callback();
+ });
} else {
this.$forceUpdate();
}
- this.$emit('change.value', value);
- let event = e;
- if (e.type === 'click' && this.$refs.input) {
- // click clear icon
- event = { ...e };
- event.target = this.$refs.input;
- event.currentTarget = this.$refs.input;
- const originalInputValue = this.$refs.input.value;
- // change input value cause e.target.value should be '' when clear input
- this.$refs.input.value = '';
- this.$emit('change', event);
- this.$emit('input', event);
- // reset input value
- this.$refs.input.value = originalInputValue;
- return;
- }
+ },
+ onChange(e) {
+ this.$emit('change.value', e.target.value);
this.$emit('change', e);
this.$emit('input', e);
},
-
handleReset(e) {
- this.setValue('', e);
- this.$nextTick(() => {
+ this.setValue('', () => {
this.focus();
});
+ resolveOnChange(this.$refs.input, e, this.onChange);
},
-
- handleChange(e) {
- const { value, composing } = e.target;
- if (composing && this.lazy) return;
- this.setValue(value, e);
- },
-
- renderClearIcon(prefixCls) {
- const { allowClear, disabled } = this.$props;
- const { stateValue } = this;
- if (
- !allowClear ||
- disabled ||
- stateValue === undefined ||
- stateValue === null ||
- stateValue === ''
- ) {
- return null;
- }
- return (
-
- );
- },
-
- renderSuffix(prefixCls) {
- const { allowClear } = this.$props;
- let suffix = getComponentFromProp(this, 'suffix');
- if (suffix || allowClear) {
- return (
-
- {this.renderClearIcon(prefixCls)}
- {suffix}
-
- );
- }
- return null;
- },
-
- renderLabeledInput(prefixCls, children) {
- const props = this.$props;
- let addonAfter = getComponentFromProp(this, 'addonAfter');
- let addonBefore = getComponentFromProp(this, 'addonBefore');
- // Not wrap when there is not addons
- if (!addonBefore && !addonAfter) {
- return children;
- }
-
- const wrapperClassName = `${prefixCls}-group`;
- const addonClassName = `${wrapperClassName}-addon`;
- addonBefore = addonBefore ?
{addonBefore} : null;
-
- addonAfter = addonAfter ?
{addonAfter} : null;
-
- const mergedWrapperClassName = {
- [`${prefixCls}-wrapper`]: true,
- [wrapperClassName]: addonBefore || addonAfter,
- };
-
- const mergedGroupClassName = classNames(`${prefixCls}-group-wrapper`, {
- [`${prefixCls}-group-wrapper-sm`]: props.size === 'small',
- [`${prefixCls}-group-wrapper-lg`]: props.size === 'large',
- });
- return (
-
-
- {addonBefore}
- {children}
- {addonAfter}
-
-
- );
- },
- renderLabeledIcon(prefixCls, children) {
- const { size } = this.$props;
- let suffix = this.renderSuffix(prefixCls);
- if (!hasPrefixSuffix(this)) {
- return children;
- }
- let prefix = getComponentFromProp(this, 'prefix');
- prefix = prefix ? (
-
- {prefix}
-
- ) : null;
-
- const affixWrapperCls = classNames(`${prefixCls}-affix-wrapper`, {
- [`${prefixCls}-affix-wrapper-sm`]: size === 'small',
- [`${prefixCls}-affix-wrapper-lg`]: size === 'large',
- });
- return (
-
- {prefix}
- {children}
- {suffix}
-
- );
- },
-
renderInput(prefixCls) {
const otherProps = omit(this.$props, [
'prefixCls',
@@ -233,8 +128,10 @@ export default {
'value',
'defaultValue',
'lazy',
+ 'size',
+ 'inputType',
]);
- const { stateValue, getInputClassName, handleKeyDown, handleChange } = this;
+ const { stateValue, handleKeyDown, handleChange, size, disabled } = this;
const inputProps = {
directives: [{ name: 'ant-input' }],
domProps: {
@@ -247,11 +144,35 @@ export default {
input: handleChange,
change: noop,
},
- class: getInputClassName(prefixCls),
+ class: getInputClassName(prefixCls, size, disabled),
ref: 'input',
key: 'ant-input',
};
- return this.renderLabeledIcon(prefixCls,
);
+ return
;
+ },
+ clearPasswordValueAttribute() {
+ // https://github.com/ant-design/ant-design/issues/20541
+ this.removePasswordTimeout = setTimeout(() => {
+ if (
+ this.$refs.input &&
+ this.$refs.input.getAttribute('type') === 'password' &&
+ this.$refs.input.hasAttribute('value')
+ ) {
+ this.$refs.input.removeAttribute('value');
+ }
+ });
+ },
+ handleChange(e) {
+ const { value, composing } = e.target;
+ if (composing && this.lazy) return;
+ this.setValue(value, this.clearPasswordValueAttribute);
+ resolveOnChange(this.$refs.input, e, this.onChange);
+ },
+ handleKeyDown(e) {
+ if (e.keyCode === 13) {
+ this.$emit('pressEnter', e);
+ }
+ this.$emit('keydown', e);
},
},
render() {
@@ -274,8 +195,28 @@ export default {
return
;
}
const { prefixCls: customizePrefixCls } = this.$props;
+ const { stateValue } = this.$data;
const getPrefixCls = this.configProvider.getPrefixCls;
const prefixCls = getPrefixCls('input', customizePrefixCls);
- return this.renderLabeledInput(prefixCls, this.renderInput(prefixCls));
+ const addonAfter = getComponentFromProp(this, 'addonAfter');
+ const addonBefore = getComponentFromProp(this, 'addonBefore');
+ const suffix = getComponentFromProp(this, 'suffix');
+ const prefix = getComponentFromProp(this, 'prefix');
+ const props = {
+ props: {
+ ...getOptionProps(this),
+ prefixCls,
+ inputType: 'input',
+ value: fixControlledValue(stateValue),
+ element: this.renderInput(prefixCls),
+ handleReset: this.handleReset,
+ addonAfter,
+ addonBefore,
+ suffix,
+ prefix,
+ },
+ on: getListeners(this),
+ };
+ return
;
},
};
diff --git a/components/input/Password.jsx b/components/input/Password.jsx
index 2065474ff..93517c04a 100644
--- a/components/input/Password.jsx
+++ b/components/input/Password.jsx
@@ -38,7 +38,10 @@ export default {
blur() {
this.$refs.input.blur();
},
- onChange() {
+ onVisibleChange() {
+ if (this.disabled) {
+ return;
+ }
this.setState({
visible: !this.visible,
});
@@ -51,7 +54,7 @@ export default {
type: this.visible ? 'eye' : 'eye-invisible',
},
on: {
- [iconTrigger]: this.onChange,
+ [iconTrigger]: this.onVisibleChange,
mousedown: e => {
// Prevent focused state lost
// https://github.com/ant-design/ant-design/issues/15173
diff --git a/components/input/ResizableTextArea.jsx b/components/input/ResizableTextArea.jsx
new file mode 100644
index 000000000..ba5b8e860
--- /dev/null
+++ b/components/input/ResizableTextArea.jsx
@@ -0,0 +1,114 @@
+import ResizeObserver from '../vc-resize-observer';
+import omit from 'omit.js';
+import classNames from 'classnames';
+import calculateNodeHeight from './calculateNodeHeight';
+import raf from '../_util/raf';
+import warning from '../_util/warning';
+import BaseMixin from '../_util/BaseMixin';
+import inputProps from './inputProps';
+import PropTypes from '../_util/vue-types';
+import { getOptionProps, getListeners } from '../_util/props-util';
+
+const TextAreaProps = {
+ ...inputProps,
+ autosize: PropTypes.oneOfType([Object, Boolean]),
+ autoSize: PropTypes.oneOfType([Object, Boolean]),
+};
+const ResizableTextArea = {
+ name: 'ResizableTextArea',
+ props: TextAreaProps,
+ data() {
+ return {
+ textareaStyles: {},
+ resizing: false,
+ };
+ },
+ mixins: [BaseMixin],
+ mounted() {
+ this.resizeTextarea();
+ },
+ beforeDestroy() {
+ raf.cancel(this.nextFrameActionId);
+ raf.cancel(this.resizeFrameId);
+ },
+ watch: {
+ value() {
+ this.$nextTick(() => {
+ this.resizeTextarea();
+ });
+ },
+ },
+ methods: {
+ resizeOnNextFrame() {
+ raf.cancel(this.nextFrameActionId);
+ this.nextFrameActionId = raf(this.resizeTextarea);
+ },
+
+ resizeTextarea() {
+ const autoSize = this.$props.autoSize || this.$props.autosize;
+ if (!autoSize || !this.$refs.textArea) {
+ return;
+ }
+ const { minRows, maxRows } = autoSize;
+ const textareaStyles = calculateNodeHeight(this.$refs.textArea, false, minRows, maxRows);
+ this.setState({ textareaStyles, resizing: true }, () => {
+ raf.cancel(this.resizeFrameId);
+ this.resizeFrameId = raf(() => {
+ this.setState({ resizing: false });
+ });
+ });
+ },
+
+ renderTextArea() {
+ const props = getOptionProps(this);
+ const { prefixCls, autoSize, autosize, disabled } = props;
+ const { textareaStyles, resizing } = this.$data;
+ warning(
+ autosize === undefined,
+ 'Input.TextArea',
+ 'autosize is deprecated, please use autoSize instead.',
+ );
+ const otherProps = omit(props, [
+ 'prefixCls',
+ 'autoSize',
+ 'autosize',
+ 'defaultValue',
+ 'allowClear',
+ 'type',
+ 'lazy',
+ 'value',
+ ]);
+ const cls = classNames(prefixCls, {
+ [`${prefixCls}-disabled`]: disabled,
+ });
+ const domProps = {};
+ // Fix https://github.com/ant-design/ant-design/issues/6776
+ // Make sure it could be reset when using form.getFieldDecorator
+ if ('value' in props) {
+ domProps.value = props.value || '';
+ }
+ const style = {
+ ...textareaStyles,
+ ...(resizing ? { overflow: 'hidden' } : null),
+ };
+ const textareaProps = {
+ attrs: otherProps,
+ domProps,
+ style,
+ class: cls,
+ on: omit(getListeners(this), 'pressEnter'),
+ };
+ return (
+
+
+
+ );
+ },
+ },
+
+ render() {
+ return this.renderTextArea();
+ },
+};
+
+export default ResizableTextArea;
diff --git a/components/input/Search.jsx b/components/input/Search.jsx
index 2e0a88770..62b72fe1a 100644
--- a/components/input/Search.jsx
+++ b/components/input/Search.jsx
@@ -1,4 +1,5 @@
import classNames from 'classnames';
+import { isMobile } from 'is-mobile';
import Input from './Input';
import Icon from '../icon';
import inputProps from './inputProps';
@@ -17,15 +18,26 @@ export default {
},
props: {
...inputProps,
- enterButton: PropTypes.oneOfType([PropTypes.bool, PropTypes.string, PropTypes.object]),
+ enterButton: PropTypes.any.def(false),
},
inject: {
configProvider: { default: () => ConfigConsumerProps },
},
methods: {
+ onChange(e) {
+ if (e && e.target && e.type === 'click') {
+ this.$emit('search', e.target.value, e);
+ }
+ this.$emit('change', e);
+ },
onSearch(e) {
+ if (this.loading || this.disabled) {
+ return;
+ }
this.$emit('search', this.$refs.input.stateValue, e);
- this.$refs.input.focus();
+ if (!isMobile({ tablet: true })) {
+ this.$refs.input.focus();
+ }
},
focus() {
this.$refs.input.focus();
@@ -34,12 +46,33 @@ export default {
blur() {
this.$refs.input.blur();
},
+ renderLoading(prefixCls) {
+ const { size } = this.$props;
+ let enterButton = getComponentFromProp(this, 'enterButton');
+ // 兼容
, 因enterButton类型为 any,此类写法 enterButton 为空字符串
+ enterButton = enterButton || enterButton === '';
+ if (enterButton) {
+ return (
+
+ );
+ }
+ return
;
+ },
renderSuffix(prefixCls) {
+ const { loading } = this;
const suffix = getComponentFromProp(this, 'suffix');
- const enterButton = getComponentFromProp(this, 'enterButton');
+ let enterButton = getComponentFromProp(this, 'enterButton');
+ // 兼容
, 因enterButton类型为 any,此类写法 enterButton 为空字符串
+ enterButton = enterButton || enterButton === '';
+ if (loading && !enterButton) {
+ return [suffix, this.renderLoading(prefixCls)];
+ }
+
if (enterButton) return suffix;
- const node = (
+ const icon = (
);
@@ -50,27 +83,31 @@ export default {
// key: 'originSuffix',
// });
// }
- return [suffix, node];
+ return [suffix, icon];
}
- return node;
+ return icon;
},
renderAddonAfter(prefixCls) {
- const { size, disabled } = this;
- const enterButton = getComponentFromProp(this, 'enterButton');
- const addonAfter = getComponentFromProp(this, 'addonAfter');
- if (!enterButton) return addonAfter;
+ const { size, disabled, loading } = this;
const btnClassName = `${prefixCls}-button`;
+ let enterButton = getComponentFromProp(this, 'enterButton');
+ enterButton = enterButton || enterButton === '';
+ const addonAfter = getComponentFromProp(this, 'addonAfter');
+ if (loading && enterButton) {
+ return [this.renderLoading(prefixCls), addonAfter];
+ }
+ if (!enterButton) return addonAfter;
const enterButtonAsElement = Array.isArray(enterButton) ? enterButton[0] : enterButton;
let button;
- if (
- enterButtonAsElement.tag === 'button' ||
- (enterButtonAsElement.componentOptions &&
- enterButtonAsElement.componentOptions.Ctor.extendOptions.__ANT_BUTTON)
- ) {
+ const isAntdButton =
+ enterButtonAsElement.componentOptions &&
+ enterButtonAsElement.componentOptions.Ctor.extendOptions.__ANT_BUTTON;
+ if (enterButtonAsElement.tag === 'button' || isAntdButton) {
button = cloneElement(enterButtonAsElement, {
- class: btnClassName,
- props: { size },
+ key: 'enterButton',
+ class: isAntdButton ? btnClassName : '',
+ props: isAntdButton ? { size } : {},
on: {
click: this.onSearch,
},
@@ -85,7 +122,7 @@ export default {
key="enterButton"
onClick={this.onSearch}
>
- {enterButton === true ?
: enterButton}
+ {enterButton === true || enterButton === '' ?
: enterButton}
);
}
@@ -101,14 +138,16 @@ export default {
prefixCls: customizePrefixCls,
inputPrefixCls: customizeInputPrefixCls,
size,
+ loading,
...others
} = getOptionProps(this);
const getPrefixCls = this.configProvider.getPrefixCls;
const prefixCls = getPrefixCls('input-search', customizePrefixCls);
const inputPrefixCls = getPrefixCls('input', customizeInputPrefixCls);
- const enterButton = getComponentFromProp(this, 'enterButton');
+ let enterButton = getComponentFromProp(this, 'enterButton');
const addonBefore = getComponentFromProp(this, 'addonBefore');
+ enterButton = enterButton || enterButton === '';
let inputClassName;
if (enterButton) {
inputClassName = classNames(prefixCls, {
@@ -137,6 +176,7 @@ export default {
on: {
pressEnter: this.onSearch,
...on,
+ change: this.onChange,
},
};
return
;
diff --git a/components/input/TextArea.jsx b/components/input/TextArea.jsx
index ee5aca4a6..a4fd9efbc 100644
--- a/components/input/TextArea.jsx
+++ b/components/input/TextArea.jsx
@@ -1,32 +1,16 @@
-import classNames from 'classnames';
-import omit from 'omit.js';
-import ResizeObserver from 'resize-observer-polyfill';
+import ClearableLabeledInput from './ClearableLabeledInput';
+import ResizableTextArea from './ResizableTextArea';
import inputProps from './inputProps';
-import calculateNodeHeight from './calculateNodeHeight';
-import hasProp, { getListeners } from '../_util/props-util';
+import hasProp, { getListeners, getOptionProps } from '../_util/props-util';
import { ConfigConsumerProps } from '../config-provider';
+import { fixControlledValue, resolveOnChange } from './Input';
+import PropTypes from '../_util/vue-types';
-function onNextFrame(cb) {
- if (window.requestAnimationFrame) {
- return window.requestAnimationFrame(cb);
- }
- return window.setTimeout(cb, 1);
-}
-
-function clearNextFrameAction(nextFrameId) {
- if (window.cancelAnimationFrame) {
- window.cancelAnimationFrame(nextFrameId);
- } else {
- window.clearTimeout(nextFrameId);
- }
-}
-function fixControlledValue(value) {
- if (typeof value === 'undefined' || value === null) {
- return '';
- }
- return value;
-}
-function noop() {}
+const TextAreaProps = {
+ ...inputProps,
+ autosize: PropTypes.oneOfType([Object, Boolean]),
+ autoSize: PropTypes.oneOfType([Object, Boolean]),
+};
export default {
name: 'ATextarea',
@@ -36,68 +20,39 @@ export default {
event: 'change.value',
},
props: {
- ...inputProps,
- autosize: [Object, Boolean],
+ ...TextAreaProps,
},
inject: {
configProvider: { default: () => ConfigConsumerProps },
},
data() {
- const { value = '', defaultValue = '' } = this.$props;
+ const value = typeof this.value === 'undefined' ? this.defaultValue : this.value;
return {
- stateValue: fixControlledValue(!hasProp(this, 'value') ? defaultValue : value),
- nextFrameActionId: undefined,
- textareaStyles: {},
+ stateValue: value,
};
},
computed: {},
watch: {
value(val) {
- this.$nextTick(() => {
- this.resizeOnNextFrame();
- });
- this.stateValue = fixControlledValue(val);
- },
- autosize(val) {
- if (!val && this.$refs.textArea) {
- this.textareaStyles = omit(this.textareaStyles, ['overflowY']);
- }
+ this.stateValue = val;
},
},
mounted() {
this.$nextTick(() => {
- this.resizeTextarea();
- this.updateResizeObserverHook();
if (this.autoFocus) {
this.focus();
}
});
},
- updated() {
- this.updateResizeObserverHook();
- },
- beforeDestroy() {
- if (this.resizeObserver) {
- this.resizeObserver.disconnect();
- }
- },
methods: {
- resizeOnNextFrame() {
- if (this.nextFrameActionId) {
- clearNextFrameAction(this.nextFrameActionId);
- }
- this.nextFrameActionId = onNextFrame(this.resizeTextarea);
- },
- // We will update hooks if `autosize` prop change
- updateResizeObserverHook() {
- if (!this.resizeObserver && this.$props.autosize) {
- // Add resize observer
- this.resizeObserver = new ResizeObserver(this.resizeOnNextFrame);
- this.resizeObserver.observe(this.$refs.textArea);
- } else if (this.resizeObserver && !this.$props.autosize) {
- // Remove resize observer
- this.resizeObserver.disconnect();
- this.resizeObserver = null;
+ setValue(value, callback) {
+ if (!hasProp(this, 'value')) {
+ this.stateValue = value;
+ this.$nextTick(() => {
+ callback && callback();
+ });
+ } else {
+ this.$forceUpdate();
}
},
handleKeyDown(e) {
@@ -106,82 +61,69 @@ export default {
}
this.$emit('keydown', e);
},
- resizeTextarea() {
- const { autosize } = this.$props;
- if (!autosize || !this.$refs.textArea) {
- return;
- }
- const { minRows, maxRows } = autosize;
- const textareaStyles = calculateNodeHeight(this.$refs.textArea, false, minRows, maxRows);
- this.textareaStyles = textareaStyles;
- },
-
- handleTextareaChange(e) {
- const { value, composing } = e.target;
- if (composing || this.stateValue === value) return;
- if (!hasProp(this, 'value')) {
- this.stateValue = value;
- this.resizeTextarea();
- } else {
- this.$forceUpdate();
- }
-
- this.$emit('change.value', value);
+ onChange(e) {
+ this.$emit('change.value', e.target.value);
this.$emit('change', e);
this.$emit('input', e);
},
+ handleChange(e) {
+ const { value, composing } = e.target;
+ if (composing || this.stateValue === value) return;
+
+ this.setValue(e.target.value, () => {
+ this.$refs.resizableTextArea.resizeTextarea();
+ });
+ resolveOnChange(this.$refs.resizableTextArea.$refs.textArea, e, this.onChange);
+ },
focus() {
- this.$refs.textArea.focus();
+ this.$refs.resizableTextArea.$refs.textArea.focus();
},
blur() {
- this.$refs.textArea.blur();
+ this.$refs.resizableTextArea.$refs.textArea.blur();
+ },
+ handleReset(e) {
+ this.setValue('', () => {
+ this.$refs.resizableTextArea.renderTextArea();
+ this.focus();
+ });
+ resolveOnChange(this.$refs.resizableTextArea.$refs.textArea, e, this.onChange);
+ },
+
+ renderTextArea(prefixCls) {
+ const props = getOptionProps(this);
+ const resizeProps = {
+ props: {
+ ...props,
+ prefixCls,
+ },
+ on: {
+ ...getListeners(this),
+ input: this.handleChange,
+ keydown: this.handleKeyDown,
+ },
+ attrs: this.$attrs,
+ };
+ return
;
},
},
render() {
- const {
- stateValue,
- handleKeyDown,
- handleTextareaChange,
- textareaStyles,
- $attrs,
- prefixCls: customizePrefixCls,
- disabled,
- } = this;
- const otherProps = omit(this.$props, [
- 'prefixCls',
- 'autosize',
- 'type',
- 'value',
- 'defaultValue',
- 'lazy',
- ]);
+ const { stateValue, prefixCls: customizePrefixCls } = this;
const getPrefixCls = this.configProvider.getPrefixCls;
const prefixCls = getPrefixCls('input', customizePrefixCls);
- const cls = classNames(prefixCls, {
- [`${prefixCls}-disabled`]: disabled,
- });
-
- const textareaProps = {
- directives: [{ name: 'ant-input' }],
- attrs: { ...otherProps, ...$attrs },
- on: {
- ...getListeners(this),
- keydown: handleKeyDown,
- input: handleTextareaChange,
- change: noop,
+ const props = {
+ props: {
+ ...getOptionProps(this),
+ prefixCls,
+ inputType: 'text',
+ value: fixControlledValue(stateValue),
+ element: this.renderTextArea(prefixCls),
+ handleReset: this.handleReset,
},
+ on: getListeners(this),
};
- return (
-
- );
+ return
;
},
};
diff --git a/components/input/__tests__/__snapshots__/Search.test.js.snap b/components/input/__tests__/__snapshots__/Search.test.js.snap
index 8825248a1..f71ea6d8e 100644
--- a/components/input/__tests__/__snapshots__/Search.test.js.snap
+++ b/components/input/__tests__/__snapshots__/Search.test.js.snap
@@ -2,4 +2,4 @@
exports[`Input.Search should support custom Button 1`] = `
`;
-exports[`Input.Search should support custom button 1`] = `
`;
+exports[`Input.Search should support custom button 1`] = `
`;
diff --git a/components/input/__tests__/__snapshots__/demo.test.js.snap b/components/input/__tests__/__snapshots__/demo.test.js.snap
index b6ff27c26..e3deb453f 100644
--- a/components/input/__tests__/__snapshots__/demo.test.js.snap
+++ b/components/input/__tests__/__snapshots__/demo.test.js.snap
@@ -10,17 +10,18 @@ exports[`renders ./components/input/demo/addon.md correctly 1`] = `
`;
-exports[`renders ./components/input/demo/allowClear.md correctly 1`] = `