Skip to content

Commit

Permalink
feat(input): add tips/mouseenter/mouseleave/paste to input (#305)
Browse files Browse the repository at this point in the history
* feat(input): add tips/mouseenter/mouseleave/paste to input

Co-authored-by: chaishi <yaoyanhuoyi@qq.com>
  • Loading branch information
chaishi and chaishi committed Jan 21, 2022
1 parent df2973a commit a65a65e
Show file tree
Hide file tree
Showing 12 changed files with 331 additions and 146 deletions.
6 changes: 6 additions & 0 deletions examples/input/demos/status.vue
Original file line number Diff line number Diff line change
@@ -1,7 +1,13 @@
<template>
<div class="tdesign-demo-block-column" style="max-width: 500px">
<t-input default-value="成功状态" />
<t-input status="success" default-value="成功状态" />
<t-input status="warning" default-value="警告状态" />
<t-input status="error" default-value="错误状态" />
<br />
<t-input default-value="普通状态" tips="这是普通文本提示" />
<t-input status="success" default-value="成功状态" tips="校验通过文本提示" />
<t-input status="warning" default-value="警告状态" tips="校验不通过文本提示" />
<t-input status="error" default-value="错误状态" tips="校验存在严重问题文本提示" />
</div>
</template>
5 changes: 3 additions & 2 deletions examples/input/input.md
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ autocomplete | Boolean | false | 是否开启自动填充功能 | N
autofocus | Boolean | false | 自动聚焦 | N
clearable | Boolean | false | 是否可清空 | N
disabled | Boolean | false | 是否禁用输入框 | N
format | Function | - | 【讨论中】指定输入框展示值的格式。TS 类型:`(value: number | number) => number | string` | N
label | String / Slot / Function | - | 左侧文本。TS 类型:`string | TNode`[通用类型定义](https://github.com/Tencent/tdesign-vue/blob/develop/src/common.ts) | N
maxcharacter | Number | - | 用户最多可以输入的字符个数,一个中文汉字表示两个字符长度。`maxcharacter``maxlength` 二选一使用 | N
maxlength | Number | - | 用户最多可以输入的文本长度。值小于等于 0 的时候,则不限制输入长度。`maxcharacter``maxlength` 二选一使用 | N
Expand All @@ -38,7 +39,7 @@ onKeypress | Function | | TS 类型:`(value: InputValue, context: { e: Keyboa
onKeyup | Function | | TS 类型:`(value: InputValue, context: { e: KeyboardEvent }) => void`<br/>释放键盘时触发 | N
onMouseenter | Function | | TS 类型:`(context: { e: MouseEvent }) => void`<br/>进入输入框时触发 | N
onMouseleave | Function | | TS 类型:`(context: { e: MouseEvent }) => void`<br/>离开输入框时触发 | N
onPaste | Function | | TS 类型:`(context: { e: ClipboardEvent<HTMLInputElement>; pasteValue: string }) => void`<br/>粘贴事件,`pasteValue` 表示粘贴板的内容 | N
onPaste | Function | | TS 类型:`(context: { e: ClipboardEvent; pasteValue: string }) => void`<br/>粘贴事件,`pasteValue` 表示粘贴板的内容 | N

### Input Events

Expand All @@ -54,4 +55,4 @@ keypress | `(value: InputValue, context: { e: KeyboardEvent })` | 按下字符
keyup | `(value: InputValue, context: { e: KeyboardEvent })` | 释放键盘时触发
mouseenter | `(context: { e: MouseEvent })` | 进入输入框时触发
mouseleave | `(context: { e: MouseEvent })` | 离开输入框时触发
paste | `(context: { e: ClipboardEvent<HTMLInputElement>; pasteValue: string })` | 粘贴事件,`pasteValue` 表示粘贴板的内容
paste | `(context: { e: ClipboardEvent; pasteValue: string })` | 粘贴事件,`pasteValue` 表示粘贴板的内容
190 changes: 116 additions & 74 deletions src/input/input.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,14 +4,16 @@ import { InputValue, TdInputProps } from './type';
import { getCharacterLength, omit } from '../utils/helper';
import getConfigReceiverMixins, { InputConfig } from '../config-provider/config-receiver';
import mixins from '../utils/mixins';

import { ClassName } from '../common';
import CLASSNAMES from '../utils/classnames';
import { emitEvent } from '../utils/event';
import { prefix } from '../config';
import props from './props';
import { renderTNodeJSX } from '../utils/render-tnode';

const name = `${prefix}-input`;
const INPUT_WRAP_CLASS = `${prefix}-input__wrap`;
const INPUT_TIPS_CLASS = `${prefix}-input__tips`;

function getValidAttrs(obj: object): object {
const newObj = {};
Expand Down Expand Up @@ -54,6 +56,20 @@ export default mixins(getConfigReceiverMixins<InputInstance, InputConfig>('input
type: this.renderType,
});
},
inputClasses(): ClassName {
return [
name,
CLASSNAMES.SIZE[this.size] || '',
{
[CLASSNAMES.STATUS.disabled]: this.disabled,
[CLASSNAMES.STATUS.focused]: this.focused,
[`${prefix}-is-${this.status}`]: this.status,
[`${prefix}-is-disabled`]: this.disabled,
[`${prefix}-is-readonly`]: this.readonly,
[`${name}--focused`]: this.focused,
},
];
},
},
watch: {
autofocus: {
Expand All @@ -67,83 +83,11 @@ export default mixins(getConfigReceiverMixins<InputInstance, InputConfig>('input
immediate: true,
},
},

created() {
this.composing = false;
},
render(h: CreateElement): VNode {
const inputEvents = getValidAttrs({
focus: this.emitFocus,
blur: this.emitBlur,
keydown: this.handleKeydown,
keyup: this.handleKeyUp,
keypress: this.handleKeypress,
// input的change事件是失去焦点或者keydown的时候执行。这与api定义的change不符,所以不做任何变化。
// eslint-disable-next-line @typescript-eslint/no-empty-function
change: () => {},
});

const wrapperAttrs = omit(this.$attrs, Object.keys(this.inputAttrs));
const wrapperEvents = omit(this.$listeners, [...Object.keys(inputEvents), 'input']);

const prefixIcon = this.renderIcon(h, this.prefixIcon, 'prefix-icon');
let suffixIcon = this.renderIcon(h, this.suffixIcon, 'suffix-icon');

const label = renderTNodeJSX(this, 'label');
const suffix = renderTNodeJSX(this, 'suffix');

const labelContent = label ? <div class={`${name}__prefix`}>{label}</div> : null;
const suffixContent = suffix ? <div class={`${name}__suffix`}>{suffix}</div> : null;

if (this.showClear) {
suffixIcon = <CloseCircleFilledIcon class={`${name}__suffix-clear`} nativeOnClick={this.emitClear} />;
}

if (this.type === 'password') {
if (this.renderType === 'password') {
suffixIcon = <BrowseOffIcon class={`${name}__suffix-clear`} nativeOnClick={this.emitPassword} />;
} else if (this.renderType === 'text') {
suffixIcon = <BrowseIcon class={`${name}__suffix-clear`} nativeOnClick={this.emitPassword} />;
}
}

const classes = [
name,
CLASSNAMES.SIZE[this.size] || '',
{
[CLASSNAMES.STATUS.disabled]: this.disabled,
[CLASSNAMES.STATUS.focused]: this.focused,
[`${prefix}-is-${this.status}`]: this.status,
[`${name}--prefix`]: prefixIcon || labelContent,
[`${name}--suffix`]: suffixIcon || suffixContent,
[`${name}--focused`]: this.focused,
},
];
return (
<div
class={classes}
onMouseenter={() => this.mouseEvent(true)}
onMouseleave={() => this.mouseEvent(false)}
{...{ attrs: wrapperAttrs, on: wrapperEvents }}
>
{prefixIcon ? <span class={[`${name}__prefix`, `${name}__prefix-icon`]}>{prefixIcon}</span> : null}
{labelContent}
<input
{...{ attrs: this.inputAttrs, on: inputEvents }}
ref="refInputElem"
class={`${name}__inner`}
value={this.value}
onInput={this.handleInput}
onCompositionend={this.onCompositionend}
/>
{suffixContent}
{suffixIcon ? (
<span class={[`${name}__suffix`, `${name}__suffix-icon`, { [`${name}__clear`]: this.showClear }]}>
{suffixIcon}
</span>
) : null}
</div>
);
},
methods: {
mouseEvent(v: boolean) {
this.isHover = v;
Expand Down Expand Up @@ -197,6 +141,12 @@ export default mixins(getConfigReceiverMixins<InputInstance, InputConfig>('input
if (this.disabled) return;
emitEvent<Parameters<TdInputProps['onKeypress']>>(this, 'keypress', this.value, { e });
},
onHandlePaste(e: ClipboardEvent) {
if (this.disabled) return;
// @ts-ignore
const clipData = e.clipboardData || window.clipboardData;
emitEvent<Parameters<TdInputProps['onPaste']>>(this, 'paste', { e, pasteValue: clipData?.getData('text/plain') });
},
emitPassword() {
const { renderType } = this;
const toggleType = renderType === 'password' ? 'text' : 'password';
Expand Down Expand Up @@ -231,5 +181,97 @@ export default mixins(getConfigReceiverMixins<InputInstance, InputConfig>('input
// 受控,重要,勿删
this.$nextTick(() => this.setInputValue(this.value));
},

onInputMouseenter(e: MouseEvent) {
this.mouseEvent(true);
this.onMouseenter?.({ e });
},

onInputMouseleave(e: MouseEvent) {
this.mouseEvent(false);
this.onMouseleave?.({ e });
},
},

render(h: CreateElement): VNode {
const inputEvents = getValidAttrs({
focus: this.emitFocus,
blur: this.emitBlur,
keydown: this.handleKeydown,
keyup: this.handleKeyUp,
keypress: this.handleKeypress,
paste: this.onHandlePaste,
// input的change事件是失去焦点或者keydown的时候执行。这与api定义的change不符,所以不做任何变化。
// eslint-disable-next-line @typescript-eslint/no-empty-function
change: () => {},
});

const wrapperAttrs = omit(this.$attrs, Object.keys(this.inputAttrs));
const wrapperEvents = omit(this.$listeners, [...Object.keys(inputEvents), 'input', 'paste']);

const prefixIcon = this.renderIcon(h, this.prefixIcon, 'prefix-icon');
let suffixIcon = this.renderIcon(h, this.suffixIcon, 'suffix-icon');

const label = renderTNodeJSX(this, 'label');
const suffix = renderTNodeJSX(this, 'suffix');

const labelContent = label ? <div class={`${name}__prefix`}>{label}</div> : null;
const suffixContent = suffix ? <div class={`${name}__suffix`}>{suffix}</div> : null;

if (this.showClear) {
suffixIcon = <CloseCircleFilledIcon class={`${name}__suffix-clear`} nativeOnClick={this.emitClear} />;
}

if (this.type === 'password') {
if (this.renderType === 'password') {
suffixIcon = <BrowseOffIcon class={`${name}__suffix-clear`} nativeOnClick={this.emitPassword} />;
} else if (this.renderType === 'text') {
suffixIcon = <BrowseIcon class={`${name}__suffix-clear`} nativeOnClick={this.emitPassword} />;
}
}

const classes = [
this.inputClasses,
{
[`${name}--prefix`]: prefixIcon || labelContent,
[`${name}--suffix`]: suffixIcon || suffixContent,
},
];
const inputNode = (
<div
class={classes}
onMouseenter={this.onInputMouseenter}
onMouseleave={this.onInputMouseleave}
{...{ attrs: wrapperAttrs, on: wrapperEvents }}
>
{prefixIcon ? <span class={[`${name}__prefix`, `${name}__prefix-icon`]}>{prefixIcon}</span> : null}
{labelContent}
<input
{...{ attrs: this.inputAttrs, on: inputEvents }}
ref="refInputElem"
class={`${name}__inner`}
value={this.value}
onInput={this.handleInput}
onCompositionend={this.onCompositionend}
/>
{suffixContent}
{suffixIcon ? (
<span class={[`${name}__suffix`, `${name}__suffix-icon`, { [`${name}__clear`]: this.showClear }]}>
{suffixIcon}
</span>
) : null}
</div>
);

const tips = renderTNodeJSX(this, 'tips');
if (tips) {
return (
<div class={INPUT_WRAP_CLASS}>
{inputNode}
<div class={`${INPUT_TIPS_CLASS} ${prefix}-input__tips--${this.status || 'normal'}`}>{tips}</div>
</div>
);
}
return inputNode;
},
});
16 changes: 14 additions & 2 deletions src/input/props.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@

/**
* 该文件为脚本自动生成文件,请勿随意修改。如需修改请联系 PMC
* updated at 2021-12-28 11:39:46
* */

import { TdInputProps } from './type';
Expand All @@ -17,6 +16,10 @@ export default {
clearable: Boolean,
/** 是否禁用输入框 */
disabled: Boolean,
/** 【讨论中】指定输入框展示值的格式 */
format: {
type: Function as PropType<TdInputProps['format']>,
},
/** 左侧文本 */
label: {
type: [String, Function] as PropType<TdInputProps['label']>,
Expand Down Expand Up @@ -56,7 +59,6 @@ export default {
/** 输入框状态 */
status: {
type: String as PropType<TdInputProps['status']>,
default: undefined as TdInputProps['status'],
validator(val: TdInputProps['status']): boolean {
return ['success', 'warning', 'error'].includes(val);
},
Expand All @@ -69,6 +71,10 @@ export default {
suffixIcon: {
type: Function as PropType<TdInputProps['suffixIcon']>,
},
/** 输入框下方提示文本,会根据不同的 `status` 呈现不同的样式 */
tips: {
type: [String, Function] as PropType<TdInputProps['tips']>,
},
/** 输入框类型 */
type: {
type: String as PropType<TdInputProps['type']>,
Expand Down Expand Up @@ -101,4 +107,10 @@ export default {
onKeypress: Function as PropType<TdInputProps['onKeypress']>,
/** 释放键盘时触发 */
onKeyup: Function as PropType<TdInputProps['onKeyup']>,
/** 进入输入框时触发 */
onMouseenter: Function as PropType<TdInputProps['onMouseenter']>,
/** 离开输入框时触发 */
onMouseleave: Function as PropType<TdInputProps['onMouseleave']>,
/** 粘贴事件,`pasteValue` 表示粘贴板的内容 */
onPaste: Function as PropType<TdInputProps['onPaste']>,
};
21 changes: 20 additions & 1 deletion src/input/type.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@

/**
* 该文件为脚本自动生成文件,请勿随意修改。如需修改请联系 PMC
* updated at 2021-12-28 11:39:46
* */

import { TNode, SizeEnum } from '../common';
Expand All @@ -28,6 +27,10 @@ export interface TdInputProps {
* @default false
*/
disabled?: boolean;
/**
* 【讨论中】指定输入框展示值的格式
*/
format?: (value: number | number) => number | string;
/**
* 左侧文本
*/
Expand Down Expand Up @@ -75,6 +78,10 @@ export interface TdInputProps {
* 组件后置图标
*/
suffixIcon?: TNode;
/**
* 输入框下方提示文本,会根据不同的 `status` 呈现不同的样式
*/
tips?: string | TNode;
/**
* 输入框类型
* @default text
Expand Down Expand Up @@ -120,6 +127,18 @@ export interface TdInputProps {
* 释放键盘时触发
*/
onKeyup?: (value: InputValue, context: { e: KeyboardEvent }) => void;
/**
* 进入输入框时触发
*/
onMouseenter?: (context: { e: MouseEvent }) => void;
/**
* 离开输入框时触发
*/
onMouseleave?: (context: { e: MouseEvent }) => void;
/**
* 粘贴事件,`pasteValue` 表示粘贴板的内容
*/
onPaste?: (context: { e: ClipboardEvent; pasteValue: string }) => void;
}

export type InputValue = string | number;

0 comments on commit a65a65e

Please sign in to comment.