1 В избранное 0 Ответвления 0

OSCHINA-MIRROR/open-hand-choerodon-ui

Присоединиться к Gitlife
Откройте для себя и примите участие в публичных проектах с открытым исходным кодом с участием более 10 миллионов разработчиков. Приватные репозитории также полностью бесплатны :)
Присоединиться бесплатно
Это зеркальный репозиторий, синхронизируется ежедневно с исходного репозитория.
Клонировать/Скачать
Input.tsx 19 КБ
Копировать Редактировать Web IDE Исходные данные Просмотреть построчно История
HughHzWu Отправлено 3 лет назад c4b6909
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725
import React, {
ChangeEventHandler,
cloneElement,
Component,
CompositionEvent,
FocusEvent,
InputHTMLAttributes,
KeyboardEvent,
KeyboardEventHandler,
ReactElement,
ReactNode,
} from 'react';
import classNames from 'classnames';
import isString from 'lodash/isString';
import isFunction from 'lodash/isFunction';
import omit from 'lodash/omit';
import Group from './Group';
import Search from './Search';
import TextArea from './TextArea';
import Icon from '../icon';
import { Size } from '../_util/enum';
import { getIeVersion, isChrome } from '../_util/browser';
import ConfigContext, { ConfigContextValue } from '../config-provider/ConfigContext';
function fixControlledValue(value: undefined | null | string) {
if (typeof value === 'undefined' || value === null) {
return '';
}
return value;
}
function isNeedTriggerAfterComposition(): boolean {
return isChrome() || getIeVersion() === 11;
}
function upperCaseString(value: any) {
if (isString(value)) {
return value.toUpperCase();
}
return undefined;
}
function lowerCaseString(value: any) {
if (isString(value)) {
return value.toLowerCase();
}
return undefined;
}
/**
* 判断是否全角
*/
function isDbc(s: string) {
let dbc = false;
if (isString(s)) {
for (let i = 0; i < s.length; i++) {
const c = s.charCodeAt(i);
if ((c > 65248) || (c === 12288)) {
dbc = true;
break;
}
}
}
return dbc;
}
/**
* 全角转半角
*/
function dbcToSbc(str: string) {
const result: string[] = [];
if (isString(str)) {
for (let i = 0; i < str.length; i++) {
const code = str.charCodeAt(i); // 获取当前字符的unicode编码
if (code >= 65281 && code <= 65373) { // 在这个unicode编码范围中的是所有的英文字母已及各种字符
result.push(String.fromCharCode(code - 65248)); // 把全角字符的unicode编码转换为对应半角字符的unicode码
} else if (code === 12288) { // 空格
result.push(' ');
} else {
result.push(str.charAt(i));
}
}
}
return result.join('');
}
export type InputSelection = {
start: number | null,
end: number | null,
} | null;
export interface AbstractInputProps<T> extends Omit<InputHTMLAttributes<T>, 'onChange' | 'onCopy' | 'size' | 'prefix'> {
prefixCls?: string;
label?: ReactNode;
showLengthInfo?: boolean | 'never';
showPasswordEye?: boolean | 'hold' | 'nohold';
labelLayout?: 'float' | 'none';
onChange?: ChangeEventHandler<T>;
onPressEnter?: KeyboardEventHandler<T>;
}
export interface InputProps extends AbstractInputProps<HTMLInputElement> {
copy?: boolean;
size?: Size;
addonBefore?: ReactNode;
addonAfter?: ReactNode;
onCopy?: (value: any) => void;
prefix?: ReactNode;
suffix?: ReactNode;
focused?: boolean;
border?: boolean;
typeCase?: 'upper' | 'lower';
dbc2sbc?: boolean;
trimAll?: boolean;
trim?: boolean;
inputChinese?: boolean;
}
export interface InputState {
value?: any;
focused?: boolean;
showPassword?: boolean;
}
export default class Input extends Component<InputProps, any> {
static displayName = 'Input';
static get contextType(): typeof ConfigContext {
return ConfigContext;
}
static Group: typeof Group;
static Search: typeof Search;
static TextArea: typeof TextArea;
static defaultProps = {
type: 'text',
disabled: false,
readOnly: false,
showLengthInfo: true,
showPasswordEye: false,
border: true,
dbc2sbc: false,
trim: false,
trimAll: false,
inputChinese: true,
labelLayout: 'float',
};
context: ConfigContextValue;
state: InputState;
input: HTMLInputElement;
rendered?: HTMLDivElement;
prefix?: HTMLSpanElement;
suffix?: HTMLSpanElement;
isOnComposition = false;
inputSelection: InputSelection;
constructor(props, context: ConfigContextValue) {
super(props, context);
this.state = {
value: typeof props.value === 'undefined' ? props.defaultValue : props.value,
focused: false,
showPassword: false,
};
}
componentDidMount() {
const { focused, autoFocus } = this.props;
if (autoFocus) {
this.setState({
focused: true,
});
}
if (typeof focused === 'boolean') {
this.setState({
focused,
});
}
this.setRenderedStyle();
}
componentWillReceiveProps(nextProps: InputProps) {
const { value } = this.state;
if ('value' in nextProps && value !== nextProps.value) {
this.setState({
value: nextProps.value,
});
}
if (nextProps.autoFocus) {
this.setState({
focused: true,
});
}
if (typeof nextProps.focused === 'boolean') {
this.setState({
focused: nextProps.focused,
});
}
if (nextProps.type !== 'password') {
this.setState({
showPassword: false,
});
}
}
componentDidUpdate(prevProps: InputProps) {
const { inputSelection } = this;
const { value } = prevProps;
if (inputSelection && value && !this.isOnComposition) {
// 在 didUpdate 时根据情况恢复光标的位置
// 如果光标的位置小于值的长度,那么可以判定属于中间编辑的情况
// 此时恢复光标的位置
// 当如果不是 onComposiotionend 触发的事件时不用修改位置
if (inputSelection.start &&
this.transformValue(value).length &&
inputSelection.start < this.transformValue(value).length) {
const input = this.input;
input.selectionStart = inputSelection.start;
input.selectionEnd = inputSelection.end;
this.inputSelection = null;
}
}
this.setRenderedStyle();
}
setRenderedStyle() {
const { rendered, input, suffix, prefix } = this;
if (rendered || input) {
let suffixWidth: string;
let prefixWidth: string;
let marginRight = '';
let marginLeft = '';
let width = '100%';
if (suffix && prefix) {
suffixWidth = `${(suffix.clientWidth || -2) + 2}px`;
prefixWidth = `${(prefix.clientWidth || -2) + 2}px`;
marginRight = suffixWidth;
marginLeft = prefixWidth;
width = `calc(100% - ${suffixWidth} - ${prefixWidth})`;
} else if (suffix) {
suffixWidth = `${(suffix.clientWidth || -2) + 2}px`;
marginRight = suffixWidth;
width = `calc(100% - ${suffixWidth})`;
} else if (prefix) {
prefixWidth = `${(prefix.clientWidth || -2) + 2}px`;
marginLeft = prefixWidth;
width = `calc(100% - ${prefixWidth})`;
}
if (rendered) {
rendered.style.marginRight = marginRight;
rendered.style.marginLeft = marginLeft;
rendered.style.width = width;
} else if (input) {
input.style.paddingRight = marginRight;
input.style.paddingLeft = marginLeft;
}
}
}
handleComposition = (e: CompositionEvent) => {
if (e.type === 'compositionend') {
// composition is end
this.isOnComposition = false;
if (isNeedTriggerAfterComposition()) {
this.handleChange(e);
}
} else {
// in composition
this.isOnComposition = true;
}
};
handleFocus = (e: FocusEvent<HTMLInputElement>) => {
const { onFocus, readOnly } = this.props;
if (!readOnly) {
this.setState({
focused: true,
});
}
if (onFocus) {
onFocus(e);
}
};
handleBlur = (e: FocusEvent<HTMLInputElement>) => {
const value = e.target.value;
const { onChange, trim, trimAll, onBlur, readOnly } = this.props;
if (!readOnly) {
this.setState({
focused: false,
});
}
let trimValue = value;
if (trim && isString(value)) {
trimValue = value.trim();
}
if (trimAll && isString(value)) {
trimValue = value.replace(/\s/g, '');
}
if (trimValue !== value) {
this.input.value = trimValue;
if (onChange && isFunction(onChange)) {
e.target.value = trimValue;
onChange(e);
}
}
if (onBlur && isFunction(onBlur)) {
onBlur(e);
}
};
handleChange = e => {
const { onChange } = this.props;
if (!this.isOnComposition) {
// 在 onChange 时记录光标的位置
if (this.input) {
this.inputSelection = {
start: this.input.selectionStart,
end: this.input.selectionEnd,
};
}
const transformValue = this.transformValue(e.target.value);
if (transformValue !== e.target.value) {
e.target.value = this.transformValue(e.target.value);
if (this.inputSelection && (this.inputSelection.start || this.inputSelection.end)) {
e.target.setSelectionRange(this.inputSelection.start, this.inputSelection.end);
this.inputSelection = null;
}
}
}
if (!('value' in this.props)) {
this.setState({ value: e.target.value });
}
if (onChange && isFunction(onChange)) {
onChange(e);
}
};
handleCopy = () => {
const { onCopy } = this.props;
this.input.select();
document.execCommand('Copy');
if (onCopy) {
onCopy(this.input.value);
}
};
handleTogglePassword = () => {
const { showPassword } = this.state;
this.setState({
showPassword: !showPassword,
});
};
handleShowPassword = () => {
this.setState({
showPassword: true,
});
};
handleHidePassword = () => {
this.setState({
showPassword: false,
});
};
handleKeyDown = (e: KeyboardEvent<HTMLInputElement>) => {
const { onPressEnter, onKeyDown } = this.props;
if (e.keyCode === 13 && onPressEnter) {
onPressEnter(e);
}
if (onKeyDown) {
onKeyDown(e);
}
};
saveInput = (node: HTMLInputElement) => {
this.input = node;
};
saveRenderedRef = (node: HTMLDivElement) => {
this.rendered = node;
};
savePrefix = (node: HTMLSpanElement) => {
this.prefix = node;
};
saveSuffix = (node: HTMLSpanElement) => {
this.suffix = node;
};
focus() {
this.input.focus();
}
blur() {
this.input.blur();
}
getPrefixCls() {
const { prefixCls } = this.props;
const { getPrefixCls } = this.context;
return getPrefixCls('input', prefixCls);
}
getInputClassName() {
const { size, copy, disabled, typeCase, showPasswordEye, type } = this.props;
const prefixCls = this.getPrefixCls();
return classNames(prefixCls, {
[`${prefixCls}-sm`]: size === Size.small,
[`${prefixCls}-lg`]: size === Size.large,
[`${prefixCls}-disabled`]: disabled,
[`${prefixCls}-upper`]: typeCase === 'upper',
[`${prefixCls}-lower`]: typeCase === 'lower',
[`${prefixCls}-has-copy`]: copy,
[`${prefixCls}-has-eye`]: showPasswordEye && type === 'password',
});
}
transformValue(v: any) {
const { typeCase, dbc2sbc = true, inputChinese } = this.props;
let value = v;
if (typeCase === 'upper') {
value = upperCaseString(v);
} else if (typeCase === 'lower') {
value = lowerCaseString(v);
}
if (dbc2sbc && isDbc(v)) {
value = dbcToSbc(v);
}
if (!inputChinese && isString(value)) {
value = value.replace(/[\u4e00-\u9fa5]/g, '');
}
return value;
}
renderCopyIcon(prefixCls?: string) {
const { copy } = this.props;
return copy ? (
<span className={`${prefixCls}-icon `} onClick={this.handleCopy}>
<Icon className={`${prefixCls}-icon-copy`} type="library_books" />
</span>
) : null;
}
renderShowPassword(prefixCls?: string) {
const { type, showPasswordEye } = this.props;
if (showPasswordEye && type === 'password') {
const { showPassword } = this.state;
const props: any = {};
if (showPasswordEye === 'nohold') {
props.onMouseDown = this.handleShowPassword;
props.onMouseLeave = this.handleHidePassword;
props.onMouseUp = this.handleHidePassword;
} else {
props.onClick = this.handleTogglePassword;
}
return (
<span className={`${prefixCls}-icon ${prefixCls}-icon-eye`} {...props}>
<Icon
className={`${prefixCls}-icon-copy`}
type={showPassword ? 'visibility' : 'visibility_off'}
/>
</span>
);
}
}
getLengthInfo(prefixCls?: string) {
const { maxLength, showLengthInfo } = this.props;
const { value } = this.state;
const inputLength = value ? value.length : 0;
return showLengthInfo !== 'never' && ((maxLength && showLengthInfo === true) || (maxLength && maxLength > 0 && inputLength === maxLength)) ? (
<div className={`${prefixCls}-length-info`}>{`${inputLength}/${maxLength}`}</div>
) : null;
}
renderFloatLabel(prefixCls?: string): ReactNode {
const { label } = this.props;
if (label) {
return (
<div className={`${prefixCls}-label-wrapper`}>
<div className={`${prefixCls}-label`}>{label}</div>
</div>
);
}
}
getSizeClassName(name: string, prefixCls?: string) {
const { size } = this.props;
return classNames(`${prefixCls}-${name}`, {
[`${prefixCls}-${name}-sm`]: size === Size.small,
[`${prefixCls}-${name}-lg`]: size === Size.large,
});
}
hasValue() {
const { value } = this.state;
return value && value.length !== 0;
}
renderLabeledIcon(children: ReactElement<any>, prefixCls?: string) {
const { props } = this;
const hasBorder = props.border && props.labelLayout === 'float';
const passwordEye = this.renderShowPassword(prefixCls);
const copyIcon = this.renderCopyIcon(prefixCls);
const floatLabel = hasBorder && this.renderFloatLabel(prefixCls);
const { className } = props;
const prefix = props.prefix ? (
<span className={`${prefixCls}-prefix`} ref={this.savePrefix}>
{props.prefix}
</span>
) : null;
const $suffix = props.showPasswordEye === 'nohold' && passwordEye ? passwordEye : props.suffix;
const $passwordEye = props.showPasswordEye === 'nohold' ? undefined : passwordEye;
const suffix = $suffix ? (
<span className={`${prefixCls}-suffix`} ref={this.saveSuffix}>
{$suffix}
</span>
) : null;
if (hasBorder) {
const { focused } = this.state;
const preProps = children.props;
children = cloneElement<InputProps>(children, {
className: classNames(preProps.className, className),
placeholder: (!floatLabel || focused) ? preProps.placeholder : null,
});
}
if ($passwordEye || copyIcon || floatLabel) {
children = (
<div
className={this.getSizeClassName('rendered', prefixCls)}
ref={this.saveRenderedRef}
>
{children}
{floatLabel}
{copyIcon}
{$passwordEye}
</div>
);
}
if (prefix || suffix) {
const affixWrapperCls = classNames(this.getSizeClassName('affix-wrapper', prefixCls), {
[`${className}`]: className && !hasBorder,
[`${prefixCls}-has-border`]: hasBorder,
});
return (
<span
className={affixWrapperCls}
style={props.style}
>
{prefix}
{cloneElement(children, { style: null })}
{suffix}
</span>
);
}
if (hasBorder) {
return (
<span className={`${prefixCls}-has-border`} style={props.style}>
{cloneElement(children, { style: null })}
</span>
);
}
return cloneElement(children, { className: classNames(className, children.props.className), style: props.style });
}
renderInput(prefixCls?: string) {
const { type } = this.props;
const { value, showPassword } = this.state;
const omits = [
'prefixCls',
'onPressEnter',
'addonBefore',
'addonAfter',
'prefix',
'suffix',
'label',
'labelLayout',
'copy',
'style',
'focused',
'showLengthInfo',
'showPasswordEye',
'size',
'border',
'form',
'onChange',
'dbc2sbc',
'typeCase',
'trim',
'trimAll',
'inputChinese',
'type',
];
const otherProps: Omit<InputProps,
'prefixCls'
| 'onPressEnter'
| 'addonBefore'
| 'addonAfter'
| 'prefix'
| 'suffix'
| 'label'
| 'labelLayout'
| 'copy'
| 'style'
| 'focused'
| 'showLengthInfo'
| 'showPasswordEye'
| 'size'
| 'border'
| 'form'
| 'onChange'
| 'dbc2sbc'
| 'typeCase'
| 'trim'
| 'trimAll'
| 'inputChinese'
| 'type'
| 'placeholder'> = omit(this.props, omits);
return this.renderLabeledIcon(
<input
{...otherProps}
value={fixControlledValue(value)}
className={this.getInputClassName()}
onKeyDown={this.handleKeyDown}
ref={this.saveInput}
onFocus={this.handleFocus}
onBlur={this.handleBlur}
onChange={this.handleChange}
onCompositionStart={this.handleComposition}
onCompositionUpdate={this.handleComposition}
onCompositionEnd={this.handleComposition}
type={showPassword ? 'text' : type}
/>,
prefixCls,
);
}
getWrapperClassName(prefixCls?: string) {
const { disabled, label, prefix, suffix } = this.props;
const { focused } = this.state;
return classNames({
[`${prefixCls}-has-value`]: this.hasValue(),
[`${prefixCls}-focused`]: focused,
[`${prefixCls}-disabled`]: disabled,
[`${prefixCls}-has-label`]: !!label,
[`${prefixCls}-has-prefix`]: !!prefix,
[`${prefixCls}-has-suffix`]: !!suffix,
});
}
renderLabeledInput(children: ReactElement<any>, prefixCls?: string) {
const props = this.props;
const wrapperClassName = `${prefixCls}-group`;
const addonClassName = `${wrapperClassName}-addon`;
const addonBefore = props.addonBefore ? (
<span className={addonClassName}>
{props.addonBefore}
</span>
) : null;
const addonAfter = props.addonAfter ? (
<span className={addonClassName}>
{props.addonAfter}
</span>
) : null;
const lengthInfo = this.getLengthInfo(prefixCls);
const className = classNames(`${prefixCls}-wrapper`, this.getWrapperClassName(prefixCls), {
[wrapperClassName]: (addonBefore || addonAfter),
});
const groupClassName = this.getSizeClassName('group-wrapper', prefixCls);
// Need another wrapper for changing display:table to display:inline-block
// and put style prop in wrapper
if (addonBefore || addonAfter) {
return (
<span
className={groupClassName}
style={props.style}
>
<span className={className}>
{addonBefore}
{cloneElement(children, { style: null })}
{addonAfter}
</span>
{lengthInfo}
</span>
);
}
if (lengthInfo) {
return (
<span className={className} style={props.style}>
{cloneElement(children, { style: null })}
{lengthInfo}
</span>
);
}
return cloneElement(children, { className: classNames(children.props.className, className) });
}
render() {
const prefixCls = this.getPrefixCls();
return this.renderLabeledInput(this.renderInput(prefixCls), prefixCls);
}
}

Комментарий ( 0 )

Вы можете оставить комментарий после Вход в систему

1
https://gitlife.ru/oschina-mirror/open-hand-choerodon-ui.git
git@gitlife.ru:oschina-mirror/open-hand-choerodon-ui.git
oschina-mirror
open-hand-choerodon-ui
open-hand-choerodon-ui
master