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

OSCHINA-MIRROR/open-hand-choerodon-ui

Присоединиться к Gitlife
Откройте для себя и примите участие в публичных проектах с открытым исходным кодом с участием более 10 миллионов разработчиков. Приватные репозитории также полностью бесплатны :)
Присоединиться бесплатно
Клонировать/Скачать
TextField.tsx 45 КБ
Копировать Редактировать Web IDE Исходные данные Просмотреть построчно История
yezongjie Отправлено год назад 2c1e86a
12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115111611171118111911201121112211231124112511261127112811291130113111321133113411351136113711381139114011411142114311441145114611471148114911501151115211531154115511561157115811591160116111621163116411651166116711681169117011711172117311741175117611771178117911801181118211831184118511861187118811891190119111921193119411951196119711981199120012011202120312041205120612071208120912101211121212131214121512161217121812191220122112221223122412251226122712281229123012311232123312341235123612371238123912401241124212431244124512461247124812491250125112521253125412551256125712581259126012611262126312641265126612671268126912701271127212731274127512761277127812791280128112821283128412851286128712881289129012911292129312941295129612971298129913001301130213031304130513061307130813091310131113121313131413151316131713181319132013211322132313241325132613271328132913301331133213331334133513361337133813391340134113421343134413451346134713481349135013511352135313541355135613571358135913601361136213631364136513661367136813691370137113721373137413751376137713781379138013811382138313841385138613871388138913901391139213931394139513961397139813991400140114021403140414051406140714081409141014111412141314141415141614171418141914201421142214231424142514261427142814291430143114321433143414351436143714381439144014411442144314441445144614471448144914501451145214531454145514561457145814591460146114621463146414651466146714681469147014711472147314741475147614771478147914801481148214831484148514861487148814891490149114921493149414951496149714981499150015011502150315041505150615071508150915101511151215131514151515161517151815191520152115221523152415251526152715281529153015311532153315341535153615371538153915401541154215431544154515461547154815491550155115521553155415551556155715581559156015611562156315641565156615671568156915701571157215731574
import React, {
Children,
cloneElement,
createElement,
CSSProperties,
DetailedHTMLProps,
HTMLAttributes,
isValidElement,
Key,
ReactElement,
ReactNode,
} from 'react';
import { DebouncedFunc, DebounceSettings } from 'lodash';
import omit from 'lodash/omit';
import defer from 'lodash/defer';
import isArray from 'lodash/isArray';
import isNumber from 'lodash/isNumber';
import isString from 'lodash/isString';
import isRegExp from 'lodash/isRegExp';
import isNil from 'lodash/isNil';
import noop from 'lodash/noop';
import defaultTo from 'lodash/defaultTo';
import debounce from 'lodash/debounce';
import classNames from 'classnames';
import { action, computed, observable, runInAction, toJS, isArrayLike } from 'mobx';
import { observer } from 'mobx-react';
import { global } from 'choerodon-ui/shared';
import KeyCode from 'choerodon-ui/lib/_util/KeyCode';
import { pxToRem, toPx } from 'choerodon-ui/lib/_util/UnitConvertor';
import { Tooltip as TextTooltip, WaitType, FieldFocusMode } from '../core/enum';
import { FormField, FormFieldProps } from '../field/FormField';
import autobind from '../_util/autobind';
import isEmpty from '../_util/isEmpty';
import isIE from '../_util/isIE';
import Icon from '../icon';
import { preventDefault, stopPropagation } from '../_util/EventManager';
import measureTextWidth from '../_util/measureTextWidth';
import Animate from '../animate';
import { GroupItemCategory, ValueChangeAction } from './enum';
import { ShowHelp } from '../field/enum';
import Record from '../data-set/Record';
import { FieldFormat } from '../data-set/enum';
import { LabelLayout } from '../form/interface';
import { getProperty } from '../form/utils';
import RenderedText, { RenderedTextProps } from './RenderedText';
import isReactChildren from '../_util/isReactChildren';
import TextFieldGroup from './TextFieldGroup';
import { findFirstFocusableElement } from '../_util/focusable';
import { hide, show } from '../tooltip/singleton';
import isOverflow from '../overflow-tip/util';
import { toRangeValue } from '../field/utils';
import { TooltipProps } from '../tooltip/Tooltip';
const defaultWrap: (node: ReactElement) => ReactElement = node => node;
export function isPlaceHolderSupport(): boolean {
if (global.PLACEHOLDER_SUPPORT !== undefined) {
return global.PLACEHOLDER_SUPPORT;
}
if (typeof window !== 'undefined') {
const support = 'placeholder' in document.createElement('input');
global.PLACEHOLDER_SUPPORT = support;
return support;
}
return true;
}
export interface TextFieldProps<V = any> extends FormFieldProps<V> {
/**
* 占位词
*/
placeholder?: string | string[];
/**
* 最小长度
*/
minLength?: number;
/**
* 最大长度
*/
maxLength?: number;
/**
* 正则校验
*/
pattern?: string | RegExp;
/**
* 自动完成
*/
autoComplete?: string;
/**
* 前缀
*/
prefix?: ReactNode;
/**
* 后缀
*/
suffix?: ReactNode;
/**
* 是否显示清除按钮
*/
clearButton?: boolean;
/**
* 前置标签
*/
addonBefore?: ReactNode;
/**
* 前置标签样式
*/
addonBeforeStyle?: CSSProperties;
/**
* 后置标签
*/
addonAfter?: ReactNode;
/**
* 后置标签样式
*/
addonAfterStyle?: CSSProperties;
/**
* 限制可输入的字符
*/
restrict?: string | RegExp;
/**
* 是否是筛选条 flat 模式
*/
isFlat?: boolean;
/**
* 触发值变更的动作, default: blur
*/
valueChangeAction?: ValueChangeAction;
/**
* 值变更间隔时间,只有在valueChangeAction为input时起作用
*/
wait?: number;
/**
* 值变更间隔类型,可选值:throttle | debounce
* @default throttle
*/
waitType?: WaitType;
groupClassName?: string;
/**
* 是否显示长度信息
*/
showLengthInfo?: boolean;
/**
* 是否显示边框
* @default true
*/
border?: boolean;
/**
* 用tooltip显示输入框内容(禁用模式下生效)
* 可选值:`none` `always` `overflow` 或自定义 tooltip
* 配置自定义tooltip属性:tooltip={['always', { theme: 'light', ... }]}
*/
tooltip?: TextTooltip | [TextTooltip, TooltipProps];
}
export class TextField<T extends TextFieldProps> extends FormField<T> {
static displayName = 'TextField';
static defaultProps = {
...FormField.defaultProps,
suffixCls: 'input',
clearButton: false,
multiple: false,
border: true,
valueChangeAction: ValueChangeAction.blur,
waitType: WaitType.debounce,
};
@observable text?: string;
type = 'text';
renderedValue: ReactNode;
tagContainer: HTMLUListElement | null;
handleChangeWait: DebouncedFunc<(...value: any[]) => void>;
addonAfterRef?: HTMLDivElement | null;
addonBeforeRef?: HTMLDivElement | null;
@observable suffixRef?: HTMLDivElement | null;
@observable renderedText?: {
text: string;
width: number;
} | undefined;
@observable renderedStartText?: {
text: string;
width: number;
} | undefined;
@observable renderedEndText?: {
text: string;
width: number;
} | undefined;
prefixWidth?: number;
lengthElement?: ReactNode;
lengthInfoWidth?: number;
suffixWidth?: number;
isSuffixClick?: boolean;
lastAnimationRecord?: Record;
get clearButton(): boolean {
const { clearButton } = this.props;
return !!clearButton && !this.readOnly && !this.disabled;
}
/**
* 是否显示长度信息
*/
@computed
get showLengthInfo(): boolean | undefined {
if ('showLengthInfo' in this.props) {
return this.props.showLengthInfo;
}
return this.getContextConfig('showLengthInfo');
}
@computed
get showHelp(): ShowHelp {
const { showHelp } = this.observableProps;
if (isString(showHelp)) {
return showHelp as ShowHelp;
}
return this.context.showHelp || this.getContextConfig('showHelp') || ShowHelp.newLine;
}
get border(): boolean | undefined {
return this.props.border;
}
constructor(props, context) {
super(props, context);
this.handleChangeWait = this.getHandleChange(props);
}
@autobind
@action
handleRenderedValueChange(text: string, width: number, rangeTarget?: 0 | 1) {
switch (rangeTarget) {
case 0: {
this.renderedStartText = {
text,
width,
};
break;
}
case 1: {
this.renderedEndText = {
text,
width,
};
break;
}
default: {
this.renderedText = {
text,
width,
};
}
}
}
clearRenderedText(rangeTarget?: 0 | 1) {
switch (rangeTarget) {
case 0: {
if (this.renderedStartText) {
runInAction(() => {
this.renderedStartText = undefined;
});
}
break;
}
case 1: {
if (this.renderedEndText) {
runInAction(() => {
this.renderedEndText = undefined;
});
}
break;
}
default: {
if (this.renderedText) {
runInAction(() => {
this.renderedText = undefined;
});
}
}
}
}
componentWillReceiveProps(nextProps, nextContext) {
super.componentWillReceiveProps(nextProps, nextContext);
const { wait, waitType } = this.props;
if (wait !== nextProps.wait || waitType !== nextProps.waitType) {
this.handleChangeWait = this.getHandleChange(nextProps);
}
}
componentWillUnmount() {
super.componentWillUnmount();
this.handleChangeWait.cancel();
}
@autobind
saveTagContainer(node) {
this.tagContainer = node;
}
@autobind
saveAddonAfterRef(node) {
this.addonAfterRef = node;
}
@autobind
saveAddonBeforeRef(node) {
this.addonBeforeRef = node;
}
saveSuffixRef(node) {
return node;
}
measureTextWidth(text: string): number {
const element = this.element && this.element.element ?
this.element.element : this.element;
const computedStyle: CSSStyleDeclaration | undefined =
element ?
getComputedStyle(element) :
undefined;
return measureTextWidth(text, computedStyle);
}
getEditorTextInfo(rangeTarget?: 0 | 1): { text: string; width: number; placeholder?: string } {
const { isFlat } = this.props;
const [startPlaceHolder, endPlaceHolder = startPlaceHolder] = this.hasFloatLabel && !this.isFocused ? [] : this.getPlaceholders();
const { text } = this;
if (rangeTarget === undefined) {
if (text !== undefined) {
return {
text,
width: isFlat ? this.measureTextWidth(text) : 0,
};
}
const { renderedText } = this;
if (renderedText) {
if (renderedText.text) {
return renderedText;
}
} else {
const unRenderedText = this.getTextNode(this.getValue());
const processedText = isNumber(unRenderedText) ? String(unRenderedText) : unRenderedText;
if (isString(processedText) && processedText.length) {
return {
text: processedText,
width: isFlat ? this.measureTextWidth(processedText) : 0,
};
}
}
if (startPlaceHolder) {
return {
text: '',
width: isFlat ? this.measureTextWidth(startPlaceHolder) : 0,
placeholder: startPlaceHolder,
};
}
return {
text: '',
width: 0,
};
}
const [startValue, endValue] = this.processRangeValue(this.isEditableLike() ? undefined : this.rangeValue);
if (rangeTarget === 0) {
const { renderedStartText } = this;
if (renderedStartText) {
if (renderedStartText.text) {
return renderedStartText;
}
} else {
const unRenderedStartText = this.getTextByValue(startValue);
const startText = isNumber(unRenderedStartText) ? String(unRenderedStartText) : unRenderedStartText;
if (isString(startText) && startText.length) {
return {
text: startText,
width: isFlat ? this.measureTextWidth(startText) : 0,
};
}
}
if (!isNil(startPlaceHolder) || !isNil(text)) {
const width = !isNil(text) ? this.measureTextWidth(text) : !isNil(startPlaceHolder) ? this.measureTextWidth(startPlaceHolder) : 0;
return {
text: '',
width: isFlat ? width : 0,
placeholder: isNil(text) ? startPlaceHolder : undefined,
};
}
}
if (rangeTarget === 1) {
const { renderedEndText } = this;
if (renderedEndText) {
if (renderedEndText.text) {
return renderedEndText;
}
} else {
const unRenderedEndText = this.getTextByValue(endValue);
const endText = isNumber(unRenderedEndText) ? String(unRenderedEndText) : unRenderedEndText;
if (isString(endText) && endText.length) {
return {
text: endText,
width: isFlat ? this.measureTextWidth(endText) : 0,
};
}
}
if (!isNil(endPlaceHolder) || !isNil(text)) {
const width = !isNil(text) ? this.measureTextWidth(text) : !isNil(endPlaceHolder) ? this.measureTextWidth(endPlaceHolder) : 0;
return {
text: '',
width: isFlat ? width : 0,
placeholder: isNil(text) ? endPlaceHolder : undefined,
};
}
}
return {
text: '',
width: 0,
};
}
isRenderEmpty() {
if (!this.multiple) {
return this.range ? isEmpty(this.getEditorTextInfo(0).text) && isEmpty(this.getEditorTextInfo(1).text) : isEmpty(this.getEditorTextInfo().text);
}
return true;
}
isEmpty() {
return (this.isEditableLike() || isEmpty(this.text)) && super.isEmpty() && this.isRenderEmpty();
}
getOmitPropsKeys(): string[] {
return super.getOmitPropsKeys().concat([
'prefix',
'suffix',
'clearButton',
'addonBefore',
'addonAfter',
'addonBeforeStyle',
'addonAfterStyle',
'restrict',
'placeholder',
'placeHolder',
'maxLengths',
'autoComplete',
'valueChangeAction',
'wait',
'waitType',
'groupClassName',
'showLengthInfo',
'border',
]);
}
getOtherProps() {
const otherProps = super.getOtherProps();
otherProps.type = this.type;
otherProps.maxLength = this.getProp('maxLength');
otherProps.onKeyDown = this.handleKeyDown;
otherProps.autoComplete = this.props.autoComplete || this.getContextConfig('textFieldAutoComplete') || 'off';
return otherProps;
}
forceBlur(e) {
const { addonBefore, addonAfter, _inTable } = this.props;
if (!_inTable || (!addonBefore && !addonAfter)) {
super.forceBlur(e);
}
}
@autobind
handleAddonBeforeKeyDown(e) {
const { _inTable, onKeyDown } = this.props;
if (_inTable && onKeyDown && e.keyCode === KeyCode.TAB && e.shiftKey) {
onKeyDown(e);
}
}
@autobind
handleAddonAfterKeyDown(e) {
const { _inTable, onKeyDown } = this.props;
if (_inTable && onKeyDown && e.keyCode === KeyCode.TAB && !e.shiftKey) {
onKeyDown(e);
}
}
@autobind
handleMultipleMouseEnter(e) {
const { onMouseEnter } = this.getOtherProps();
if (onMouseEnter) {
onMouseEnter(e);
}
show(e.currentTarget, {
title: this.getMultipleText(),
});
this.tooltipShown = true;
}
@autobind
handleMultipleMouseLeave() {
const { onMouseLeave } = this.getOtherProps();
if (onMouseLeave) {
onMouseLeave();
}
this.handleHelpMouseLeave();
}
@autobind
handleHelpMouseEnter(e) {
const { getTooltipTheme, getTooltipPlacement } = this.context;
const { helpTooltipProps } = this;
let helpTooltipCls = `${this.getContextConfig('proPrefixCls')}-tooltip-popup-help`;
if (helpTooltipProps && helpTooltipProps.popupClassName) {
helpTooltipCls = helpTooltipCls.concat(' ', helpTooltipProps.popupClassName)
}
show(e.currentTarget, {
theme: getTooltipTheme('help'),
placement: getTooltipPlacement('help'),
title: this.getDisplayProp('help'),
...helpTooltipProps,
popupClassName: helpTooltipCls,
});
this.tooltipShown = true;
}
@autobind
handleHelpMouseLeave() {
if (this.tooltipShown) {
hide();
this.tooltipShown = false;
}
}
getWrapperClassNames(...args): string {
const { prefixCls, multiple, range, border } = this;
const suffix = this.getSuffix();
const prefix = this.getPrefix();
return super.getWrapperClassNames(
{
[`${prefixCls}-suffix-button`]: isValidElement<{ onClick }>(suffix),
[`${prefixCls}-multiple`]: multiple,
[`${prefixCls}-range`]: range,
[`${prefixCls}-border`]: border,
[`${prefixCls}-prefix-button`]: isValidElement<{ onClick }>(prefix),
},
...args,
);
}
renderWrapper(): ReactNode {
return this.renderGroup();
}
getRenderedValue(): ReactNode {
if (this.range) {
return this.renderRangeValue(toRangeValue(this.getValue(), this.range));
}
return this.processRenderer(this.getValue());
}
showTooltip(e): boolean {
if (super.showTooltip(e)) {
return true;
}
const { getTooltip, getTooltipTheme, getTooltipPlacement } = this.context;
const { tooltip: inputTooltip } = this.props;
const disabledTooltip = getTooltip('text-field-disabled');
const { element, renderedValue } = this;
const title = this.getRenderedValue();
const judgeOverflowElement = renderedValue ? element.parentNode.previousElementSibling : element;
const tooltip = this.disabled ? disabledTooltip : inputTooltip;
const tooltipPlacement = this.disabled ? getTooltipPlacement('text-field-disabled') : getTooltipPlacement('output');
const tooltipTheme = this.disabled ? getTooltipTheme('text-field-disabled') : getTooltipTheme('output');
if (judgeOverflowElement && !this.multiple && title) {
if (tooltip === TextTooltip.always || (tooltip === TextTooltip.overflow && isOverflow(judgeOverflowElement))) {
show(judgeOverflowElement, {
title,
placement: tooltipPlacement || 'right',
theme: tooltipTheme,
});
return true;
}
if (isArrayLike(tooltip)) {
const tooltipType = tooltip[0];
const TextTooltipProps = tooltip[1] || {};
const { mouseEnterDelay } = TextTooltipProps;
if (tooltipType === TextTooltip.always || (tooltipType === TextTooltip.overflow && isOverflow(judgeOverflowElement))) {
show(judgeOverflowElement, {
title: TextTooltipProps.title ? TextTooltipProps.title : title,
placement: tooltipPlacement || 'right',
theme: tooltipTheme,
...TextTooltipProps,
}, mouseEnterDelay);
return true;
}
}
}
return false;
}
renderInputElement(): ReactNode {
const { addonBefore, addonAfter, isFlat } = this.props;
const renderedValue = this.renderRenderedValue(undefined, { isFlat });
this.renderedValue = renderedValue;
// 先计算lengthElement,然后计算suffix,再计算clearButton,设置right和输入框paddingRight,避免重叠
this.renderLengthElement();
const suffix = this.getSuffix();
const button = this.getInnerSpanButton();
const prefix = this.getPrefix();
const input = this.getWrappedEditor(renderedValue);
const otherPrevNode = this.getOtherPrevNode();
const otherNextNode = this.getOtherNextNode();
const placeholderDiv = renderedValue ? undefined : this.renderPlaceHolder();
const floatLabel = this.renderFloatLabel();
const multipleHolder = this.renderMultipleHolder();
const wrapperProps = this.getWrapperProps();
// 修复设置宽度导致拥有addon出现宽度超出
if (addonAfter || addonBefore) {
wrapperProps.style = omit(wrapperProps.style, 'width');
}
// 修复ie下出现多层model导致的输入框遮盖问题
// fixed the input would shadow each other in ie brower
const ZIndexOfIEProps: { style: CSSProperties } | {} = isIE() ? { style: { zIndex: 'auto' } } : {};
const element = (
<span key="element" {...wrapperProps}>
{multipleHolder}
{otherPrevNode}
{placeholderDiv}
{renderedValue}
<label {...ZIndexOfIEProps} onMouseDown={this.handleMouseDown}>
{prefix}
{input}
{floatLabel}
{suffix}
{button}
</label>
</span>
);
if (otherNextNode) {
return (
<>
{element}
{otherNextNode}
</>
);
}
return element;
}
renderGroup(): ReactNode {
const {
showHelp,
prefixCls,
props: { addonBefore, addonAfter, addonBeforeStyle, addonAfterStyle, groupClassName, _inTable, onBlur, tabIndex },
} = this;
const inputElement = this.renderInputElement();
const help = showHelp === ShowHelp.tooltip ? this.renderTooltipHelp() : null;
if (!addonBefore && !addonAfter && !help) {
return inputElement;
}
const classString = classNames(`${prefixCls}-group`, groupClassName, {
[`${prefixCls}-float-label-group`]: this.hasFloatLabel,
});
return (
<TextFieldGroup key="wrapper" prefixCls={prefixCls} onBlur={_inTable && (addonBefore || addonAfter) ? onBlur : undefined}>
<div className={classString}>
{
this.wrapGroupItem(addonBefore, GroupItemCategory.before, {
style: addonBeforeStyle,
ref: this.saveAddonBeforeRef,
onKeyDown: this.handleAddonBeforeKeyDown,
}, { tabIndex })
}
{this.wrapGroupItem(inputElement, GroupItemCategory.input)}
{this.wrapGroupItem(help, GroupItemCategory.help)}
{this.wrapGroupItem(addonAfter, GroupItemCategory.after, {
style: addonAfterStyle,
ref: this.saveAddonAfterRef,
onKeyDown: this.handleAddonAfterKeyDown,
}, { tabIndex })}
</div>
</TextFieldGroup>
);
}
renderTooltipHelp(): ReactNode {
const help = this.getDisplayProp('help');
if (help) {
return (
<Icon
type="help"
onMouseEnter={this.handleHelpMouseEnter}
onMouseLeave={this.handleHelpMouseLeave}
/>
);
}
}
renderLengthInfo(maxLength?: number, inputLength?: number): ReactNode {
const { prefixCls } = this;
return maxLength && maxLength > 0 ? (
<div key="length-info" className={`${prefixCls}-length-info`}>{`${inputLength}/${maxLength}`}</div>
) : null;
}
// 处理 form 中的 labelLayout 为 placeholder 情况避免以前 placeholder 和 label 无法区分彼此。
getPlaceholders(): string[] {
const { dataSet, record, props, labelLayout } = this;
const placeholderOrigin = this.getProp('placeholder');
let placeholder = placeholderOrigin;
if (labelLayout === LabelLayout.placeholder) {
const label = getProperty(props, 'label', dataSet, record);
placeholder = label && !this.isFocused ? label : placeholderOrigin || label;
}
const holders: string[] = [];
return placeholder ? holders.concat(toJS(placeholder)) : holders;
}
wrapGroupItem(node: ReactNode, category: GroupItemCategory, props?: DetailedHTMLProps<HTMLAttributes<HTMLDivElement>, HTMLDivElement>, childrenProps?: any): ReactNode {
if (!node) {
return null;
}
const { prefixCls } = this;
const children = childrenProps ? Children.map(node, (child) => {
if (isValidElement<any>(child) && !isString(child.type)) {
return cloneElement<any>(child, childrenProps);
}
return child;
}) : node;
return <div className={`${prefixCls}-group-${category}`} {...props}>{children}</div>;
}
setRangeTarget(target) {
if (this.text !== undefined) {
this.prepareSetValue(this.text || this.emptyValue);
this.setText();
}
super.setRangeTarget(target);
defer(() => this.isFocused && this.select());
}
renderRangeEditor(props) {
const { isFlat } = this.props;
const { prefixCls, rangeTarget, isFocused, editable } = this;
const [startValue = '', endValue = ''] = this.processRangeValue(this.isEditableLike() ? undefined : this.rangeValue);
const startRenderedValue = this.renderRenderedValue(startValue, {
key: 'startRenderedText',
className: `${prefixCls}-range-start-rendered-value`,
rangeTarget: 0,
isFlat,
});
const startStyle: CSSProperties = {};
const endStyle: CSSProperties = {};
const endRenderedValue = this.renderRenderedValue(endValue, {
key: 'endRenderedText',
className: `${prefixCls}-range-end-rendered-value`,
rangeTarget: 1,
isFlat,
});
const { text: startText, placeholder: startPlaceholder, width: startWidth } = this.getEditorTextInfo(0);
const { text: endText, placeholder: endPlaceholder, width: endWidth } = this.getEditorTextInfo(1);
const editorStyle = {} as CSSProperties;
if (rangeTarget === 1) {
editorStyle.right = 0;
} else {
editorStyle.left = 0;
}
if (!editable) {
editorStyle.textIndent = -10000;
editorStyle.color = 'transparent';
}
// 筛选条默认宽度处理
const addWidth = 4 + (this.isFocused && ((rangeTarget === 0 && startText) || (rangeTarget === 1 && endText)) ? 4 : 0);
if (isFlat) {
startStyle.width = startWidth + addWidth;
startStyle.boxSizing = 'content-box';
endStyle.width = endWidth + addWidth;
endStyle.boxSizing = 'content-box';
editorStyle.width = (
!isNil(rangeTarget)
? rangeTarget === 0 ? startWidth + addWidth : endWidth + addWidth
: undefined);
editorStyle.boxSizing = !isNil(rangeTarget) ? 'content-box' : undefined;
}
if (startRenderedValue && (!editable || !isFocused)) {
startStyle.textIndent = -10000;
startStyle.color = 'transparent';
}
if (endRenderedValue && (!editable || !isFocused)) {
endStyle.textIndent = -10000;
endStyle.color = 'transparent';
}
const splitClassNames = classNames(`${prefixCls}-range-split`, {
[`${prefixCls}-range-split-custom`]: this.rangeSeparator !== '~',
});
return (
<span key="text" className={`${prefixCls}-range-text`}>
{startRenderedValue}
{endRenderedValue}
{
!this.disabled && (
<input
{...props}
className={this.getInputClassString(`${prefixCls}-range-input`)}
key="text"
value={this.getRangeInputValue(startText, endText)}
placeholder={
!editable || rangeTarget === undefined || !this.isFocused
? '' : rangeTarget === 0 ? startPlaceholder : endPlaceholder
}
readOnly={!editable}
style={editorStyle}
/>
)
}
<input
tabIndex={-1}
className={`${prefixCls}-range-start`}
onChange={noop}
onMouseDown={this.handleRangeStart}
value={editable && rangeTarget === 0 && isFocused ? '' : startText}
placeholder={editable && rangeTarget === 0 && isFocused ? '' : startPlaceholder}
disabled={props.disabled === undefined ? false : props.disabled}
style={startStyle}
readOnly
/>
<span className={splitClassNames}>{(!isFlat || startPlaceholder || endPlaceholder || !this.isEmpty()) && this.rangeSeparator}</span>
<input
tabIndex={-1}
className={`${prefixCls}-range-end`}
onChange={noop}
onMouseDown={this.handleRangeEnd}
value={editable && rangeTarget === 1 && isFocused ? '' : endText}
placeholder={editable && rangeTarget === 1 && isFocused ? '' : endPlaceholder}
disabled={props.disabled === undefined ? false : props.disabled}
style={endStyle}
readOnly
/>
</span>
);
}
getRangeInputValue(startText: string, endText: string): string {
const { rangeTarget, text } = this;
return (
rangeTarget === undefined || !this.isFocused
? '' : text === undefined ? rangeTarget === 0 ? startText : endText : text
);
}
getInputClassString(className: string): string {
return className;
}
renderMultipleEditor(props: T) {
const { text } = this;
const editorStyle = {} as CSSProperties;
if (!this.editable) {
editorStyle.position = 'absolute';
editorStyle.left = 0;
editorStyle.top = 0;
editorStyle.zIndex = -1;
props.readOnly = true;
} else if (text) {
editorStyle.width = pxToRem(this.measureTextWidth(text), true)!;
}
return (
<li key="text">
<input {...(props as object)} value={text || ''} style={editorStyle} />
</li>
);
}
getWrappedEditor(renderedValue?: ReactNode): ReactNode {
return this.getEditor(defaultWrap, renderedValue);
}
getClassName(...props): string | undefined {
const { prefixCls, format } = this;
return super.getClassName(
{
[`${prefixCls}-${format}`]: [
FieldFormat.uppercase,
FieldFormat.lowercase,
FieldFormat.capitalize,
].includes(format as FieldFormat),
},
...props,
);
}
/**
* 处理 flat 多选tooltip text
*/
getMultipleText() {
const values = this.getValues();
const repeats: Map<any, number> = new Map<any, number>();
const texts = values.map((v) => {
const key = this.getValueKey(v);
const repeat = repeats.get(key) || 0;
const text = this.processText(this.processValue(v));
repeats.set(key, repeat + 1);
if (!isNil(text)) {
return text;
}
return undefined;
});
return texts.join('');
}
@autobind
getEditor(wrap: (node: ReactNode) => ReactNode, renderedValue?: ReactNode): ReactNode {
const {
prefixCls,
multiple,
range,
props: { isFlat },
} = this;
const { onFocus, onBlur, onMouseEnter, onMouseLeave, ...otherProps } = this.getOtherProps();
if (multiple) {
const { record } = this;
const { tags: multipleTags, isOverflowMaxTagCount } = this.renderMultipleValues();
const isOverflow = isOverflowMaxTagCount || isFlat;
const tags = (
<Animate
component="ul"
componentProps={{
ref: this.saveTagContainer,
onScroll: stopPropagation,
}}
transitionName={!record || record === this.lastAnimationRecord ? 'zoom' : ''}
exclusive
onEnd={this.handleTagAnimateEnd}
onEnter={this.handleTagAnimateEnter}
>
{multipleTags}
{
range
? this.renderRangeEditor(otherProps)
: this.renderMultipleEditor({
...otherProps,
className: `${prefixCls}-multiple-input`,
} as T)
}
</Animate>
);
this.lastAnimationRecord = record;
this.setInputStylePadding(otherProps);
return wrap(
<div
key="text"
className={otherProps.className}
style={otherProps.style}
onFocus={onFocus}
onBlur={onBlur}
onMouseEnter={isOverflow ? this.handleMultipleMouseEnter : onMouseEnter}
onMouseLeave={isOverflow ? this.handleMultipleMouseLeave : onMouseLeave}
>
{tags}
</div>,
);
}
if (range) {
return wrap(
<span
key="text"
className={otherProps.className}
style={otherProps.style}
onFocus={onFocus}
onBlur={onBlur}
onMouseEnter={onMouseEnter}
onMouseLeave={onMouseLeave}
>
{this.renderRangeEditor(otherProps)}
</span>,
);
}
const editorTextInfo = this.getEditorTextInfo();
if (renderedValue && (!this.editable || !this.isFocused)) {
otherProps.style = {
...otherProps.style,
textIndent: -10000,
color: 'transparent',
};
}
// 筛选条默认宽度处理
if (isFlat) {
otherProps.style = {
...otherProps.style,
width: editorTextInfo.width + 4,
boxSizing: 'content-box',
};
}
// 兼容继承的组件
if (this.showLengthInfo && !this.lengthElement) {
this.renderLengthElement();
}
otherProps.className = this.getInputClassString(otherProps.className);
this.setInputStylePadding(otherProps);
const childNodes: ReactNode[] = [
<input
key="text"
{...otherProps}
onFocus={onFocus}
onBlur={onBlur}
onMouseEnter={onMouseEnter}
onMouseLeave={onMouseLeave}
placeholder={editorTextInfo.placeholder}
value={editorTextInfo.text}
readOnly={!this.editable}
/>,
];
if (this.showLengthInfo && this.lengthElement) {
childNodes.push(this.lengthElement);
}
return wrap(
<>
{childNodes}
</>,
);
}
getSuffixWidth() {
let wrapperWidth = 0;
if (this.suffixRef) {
wrapperWidth = this.suffixRef.getBoundingClientRect().width;
}
return this.suffixWidth ? Math.max(this.suffixWidth, wrapperWidth): this.suffixWidth;
}
setInputStylePadding(otherProps: any): void {
// 存在lengthInfo, 或suffix, 或clearButton, 计算paddingRight
if (this.lengthInfoWidth || this.getSuffixWidth() || this.clearButton) {
let paddingRight = this.isSuffixClick
? defaultTo(this.lengthInfoWidth, 0) + defaultTo(this.getSuffixWidth(), 0) + (this.clearButton ? toPx('0.18rem')! : 0)
: defaultTo(this.lengthInfoWidth, 0) + Math.max(defaultTo(this.getSuffixWidth(), 0), (this.clearButton ? toPx('0.18rem')! : 0));
if (this.lengthInfoWidth && !this.getSuffixWidth() && !this.clearButton) {
paddingRight += toPx('0.03rem')!;
}
if (paddingRight >= toPx('0.25rem')!) {
const pr = `${Number(pxToRem(paddingRight + 2, true)?.split('rem')[0]).toFixed(3)}rem`;
otherProps.style = {
...otherProps.style,
paddingRight: pr,
};
}
}
if (this.prefixWidth && this.prefixWidth > toPx('0.24rem')!) {
otherProps.style = {
...otherProps.style,
paddingLeft: pxToRem(this.prefixWidth + toPx('0.02rem')!, true),
};
}
}
renderLengthElement(): void {
const { multiple, range, showLengthInfo } = this;
if (!multiple && !range && showLengthInfo) {
const editorTextInfo = this.getEditorTextInfo();
const inputLength = editorTextInfo.text.length;
const maxLength = this.getProp('maxLength');
this.lengthElement = this.renderLengthInfo(maxLength, inputLength);
if (this.lengthElement) {
this.lengthInfoWidth = this.measureTextWidth(`${inputLength} / ${maxLength}`);
} else {
this.lengthInfoWidth = undefined;
}
} else {
this.lengthElement = undefined;
this.lengthInfoWidth = undefined;
}
}
getSuffix(): ReactNode {
const { suffix = this.getDefaultSuffix() } = this.props;
if (suffix) {
return this.wrapperSuffix(suffix);
}
this.suffixWidth = undefined;
}
getDefaultSuffix(): ReactNode {
return undefined;
}
@action
wrapperSuffix(children: ReactNode, props?: any): ReactNode {
const { prefixCls, clearButton } = this;
let divStyle = {};
if (isValidElement<any>(children)) {
this.suffixWidth = toPx('0.21rem');
if (children.props && children.props.style) {
divStyle = {
width: children.props.style.width,
};
this.suffixWidth = defaultTo(toPx(children.props.style.width), toPx('0.21rem'));
}
const { type } = children;
const { onClick, ...otherProps } = children.props;
if (onClick) {
children = createElement(type, otherProps);
props = {
onClick,
...props,
};
}
} else if (children && children !== true) {
this.suffixWidth = this.measureTextWidth(children.toString()) + toPx('0.02rem')!;
divStyle = {
width: pxToRem(this.getSuffixWidth(), true),
};
} else {
this.suffixWidth = undefined;
}
const isSuffixClick = props && props.onClick;
this.isSuffixClick = isSuffixClick;
const classString = classNames(`${prefixCls}-suffix`, {
[`${prefixCls}-allow-clear`]: clearButton && !isSuffixClick,
});
const right = pxToRem(this.lengthInfoWidth ? this.lengthInfoWidth + toPx('0.03rem')! : undefined, true);
return (
<div
className={classString}
style={{ ...divStyle, right }}
onMouseDown={preventDefault}
{...props}
ref={this.saveSuffixRef}
>
{children}
</div>
);
}
getPrefix(): ReactNode {
const { prefix } = this.props;
let wrapperPrefixNode;
if (prefix) {
wrapperPrefixNode = this.wrapperPrefix(prefix);
} else {
this.prefixWidth = undefined;
}
runInAction(() => {
if (this.prefixWidth && this.prefixWidth > toPx('0.24rem')!) {
if (this.floatLabelOffsetX !== this.prefixWidth - toPx('0.24rem')!) {
this.floatLabelOffsetX = this.prefixWidth - toPx('0.24rem')!;
}
} else if (this.floatLabelOffsetX !== undefined) {
this.floatLabelOffsetX = undefined;
}
});
return wrapperPrefixNode;
}
wrapperPrefix(children: ReactNode): ReactNode {
const { prefixCls } = this;
let divStyle = {};
if (isValidElement<any>(children)) {
this.prefixWidth = toPx('0.24rem')!;
if (children.props && children.props.style) {
divStyle = {
width: children.props.style.width,
};
const calculateWidth = toPx(children.props.style.width);
this.prefixWidth = calculateWidth != null ? calculateWidth : toPx('0.24rem')!;
}
} else if (children && children !== true) {
this.prefixWidth = this.measureTextWidth(children.toString()) + toPx('0.04rem')!;
divStyle = {
width: pxToRem(this.prefixWidth > toPx('0.24rem')! ? this.prefixWidth : undefined, true),
};
} else {
this.prefixWidth = undefined;
}
return <div className={`${prefixCls}-prefix`} style={{ ...divStyle }}>{children}</div>;
}
renderMultipleHolder() {
const { name, multiple, props: { isFlat } } = this;
let width: string | number = 'auto';
if (isFlat) {
const hasValue = !this.isEmpty();
const placeholder = this.hasFloatLabel ? undefined : this.getPlaceholders()[0];
width = hasValue ? 'auto' : this.measureTextWidth(placeholder || '') + toPx('0.22rem')! + (this.getSuffix() ? toPx('0.20rem')! : 0);
}
if (multiple) {
return (
<input
key="value"
className={`${this.prefixCls}-multiple-value`}
value={this.toValueString(this.getValue()) || ''}
name={name}
onChange={noop}
style={{ width: isFlat ? width : 'auto' }}
/>
);
}
}
getOtherPrevNode(): ReactNode {
return undefined;
}
getOtherNextNode(): ReactNode {
return undefined;
}
renderPlaceHolder(): ReactNode {
if ((this.multiple || !isPlaceHolderSupport()) && (!this.hasFloatLabel || this.isFocused) && !this.range) {
return this.getPlaceHolderNode();
}
}
renderRenderedValue(value: any | undefined, props: RenderedTextProps & { key?: Key }): ReactNode {
const { prefixCls, range, multiple } = this;
const { rangeTarget } = props;
const noRangeValue = rangeTarget === undefined;
if ((!range && !multiple) || !noRangeValue) {
const hidden = this.editable && this.isFocused;
if (!hidden || isReactChildren(this.processValue(noRangeValue ? this.getValue() : value))) {
const text = this.processRenderer(noRangeValue ? this.getValue() : value);
if (isReactChildren(text)) {
return (
<RenderedText
key="renderedText"
prefixCls={prefixCls}
onContentChange={this.handleRenderedValueChange}
hidden={hidden}
{...props}
>
{toJS(text)}
</RenderedText>
);
}
}
}
this.clearRenderedText(rangeTarget);
}
getPlaceHolderNode(): ReactNode {
const { prefixCls } = this;
const [placeholder] = this.getPlaceholders();
if (placeholder) {
let divStyle;
if (this.prefixWidth && this.prefixWidth > toPx('0.24rem')!) {
divStyle = {
paddingLeft: pxToRem(this.prefixWidth + toPx('0.05rem')!, true),
};
}
return (
<div className={`${prefixCls}-placeholder`} style={{ ...divStyle }}>
<span className={`${prefixCls}-placeholder-inner`}>{placeholder}</span>
</div>
);
}
}
getInnerSpanButton(): ReactNode {
const {
clearButton,
prefixCls,
} = this;
if (clearButton) {
let right: number | undefined;
if (this.lengthInfoWidth || this.getSuffixWidth()) {
right = this.isSuffixClick
? defaultTo(this.lengthInfoWidth, 0) + defaultTo(this.getSuffixWidth(), 0)
: this.lengthInfoWidth;
}
return this.wrapperInnerSpanButton(
<Icon
type="close"
onClick={this.handleClearButtonClick}
onMouseDown={this.handleInnerButtonMouseDown}
/>,
{
className: `${prefixCls}-clear-button`,
style: { right: pxToRem(right, true) },
},
);
}
}
wrapperInnerSpanButton(children: ReactNode, props: any = {}): ReactNode {
const { prefixCls } = this;
const { className, ...otherProps } = props;
return (
<div
key="inner-button"
{...otherProps}
className={classNames(`${prefixCls}-inner-button`, className)}
>
{children}
</div>
);
}
handleTagAnimateEnd() {
// noop
}
@autobind
handleTagAnimateEnter() {
const { tagContainer } = this;
const { style } = this.props;
if (tagContainer && style && style.height) {
if (tagContainer.scrollTo) {
tagContainer.scrollTo(0, tagContainer.getBoundingClientRect().height);
} else {
tagContainer.scrollTop = tagContainer.getBoundingClientRect().height;
}
}
}
@autobind
handleRangeStart(event) {
// 进行切换的时候默认不会收起 popup 因为点击start的时候也会触发 trigger 的 handleClick
// 导致在设置了 isClickToHide 的情况下回收起
// handleRangeEnd 同理
if (this.rangeTarget === 1 && this.isFocused) {
event.preventDefault();
}
this.setRangeTarget(0);
}
@autobind
handleRangeEnd(event) {
if (this.rangeTarget === 0 && this.isFocused) {
event.preventDefault();
}
this.setRangeTarget(1);
}
@autobind
handleKeyDown(e) {
if (!this.disabled && !this.readOnly) {
if (e.keyCode === KeyCode.TAB) {
const { _inTable } = this.props;
if (_inTable) {
if (e.shiftKey) {
const { addonBeforeRef } = this;
if (addonBeforeRef) {
const focusableElement = findFirstFocusableElement(addonBeforeRef);
if (focusableElement) {
focusableElement.focus();
e.preventDefault();
}
}
} else {
const { addonAfterRef } = this;
if (addonAfterRef) {
const focusableElement = findFirstFocusableElement(addonAfterRef);
if (focusableElement) {
focusableElement.focus();
e.preventDefault();
}
}
}
}
const { range } = this;
if (range) {
if (e.shiftKey) {
if (this.rangeTarget === 1) {
this.setRangeTarget(0);
e.preventDefault();
}
} else if (this.rangeTarget === 0) {
this.setRangeTarget(1);
e.preventDefault();
}
}
}
if (this.multiple) {
if (!this.text) {
switch (e.keyCode) {
case KeyCode.DELETE:
this.clear();
break;
case KeyCode.BACKSPACE:
this.removeLastValue();
break;
default:
}
}
} else {
const { clearButton } = this.props;
if (clearButton && !this.editable) {
switch (e.keyCode) {
case KeyCode.DELETE:
case KeyCode.BACKSPACE:
this.clear();
break;
default:
}
}
}
} else if (isIE()) {
/**
* 修复ie出现点击backSpace的页面回到上一页问题
*/
if (e.keyCode === KeyCode.BACKSPACE) {
e.preventDefault();
}
}
super.handleKeyDown(e);
}
@autobind
handleMouseDown(e) {
if (e.target !== this.element) {
if (!this.disabled) {
e.preventDefault();
}
if (!this.isFocused) {
this.focus();
}
}
}
@autobind
handleClearButtonClick(e) {
e.preventDefault();
this.setRangeTarget(0);
this.clear();
}
@autobind
handleFocus(e) {
super.handleFocus(e);
defer(() => this.isFocused && this.select());
}
@autobind
handleBlur(e) {
if (!e.isDefaultPrevented()) {
if (this.editable || this.isEditableLike()) {
this.syncValueOnBlur(e.target.value);
} else if (!this.getValues().length) {
this.setValue(null);
}
}
super.handleBlur(e);
}
@autobind
handleInnerButtonMouseDown(e) {
if (!this.isFocus) {
e.stopPropagation();
}
}
@action
setValue(value: any, noVaidate?: boolean, reserveParam?: boolean): void {
super.setValue(value, noVaidate);
if (!reserveParam) {
this.setText(undefined);
}
if (this.tooltipShown) {
hide();
this.tooltipShown = false;
}
}
getTextNode(value?: any) {
return this.text === undefined || this.isEditableLike() ? super.getTextNode(value) : this.text;
}
@action
setText(text?: string): void {
this.text = text;
}
select() {
const { element } = this;
const fieldFocusMode = this.getContextConfig('fieldFocusMode');
if (element && this.editable && fieldFocusMode === FieldFocusMode.checked) {
element.select();
}
}
getHandleChange(props): DebouncedFunc<(...value: any[]) => void> {
const { wait, waitType } = props;
if (wait && waitType) {
const options: DebounceSettings = { leading: true, trailing: true };
if (waitType === WaitType.throttle) {
options.trailing = false;
options.maxWait = wait;
} else if (waitType === WaitType.debounce) {
options.leading = false;
}
return debounce(this.prepareSetValue, wait, options);
}
return debounce(this.prepareSetValue, 0);
}
@autobind
handleChange(e) {
const {
target,
type,
target: { value },
} = e;
const { valueChangeAction } = this.props;
if (type === 'compositionend') {
this.lock = false;
}
const restricted = this.restrictInput(value);
if (restricted !== value) {
const selectionEnd = target.selectionEnd + restricted.length - value.length;
target.value = restricted;
target.setSelectionRange(selectionEnd, selectionEnd);
}
this.setText(restricted);
if (!this.isFocus || (!this.lock && valueChangeAction === ValueChangeAction.input && this.isValidInput(restricted))) {
this.handleChangeWait(restricted);
}
}
restrictInput(value: string): string {
const { restrict } = this.props;
if (restrict && !this.lock) {
const pattern = isRegExp(restrict) ? restrict : new RegExp(`[^${restrict}]+`, 'g');
return value.replace(pattern, '');
}
return value;
}
toValueString(value: any): string | undefined {
if (isArray(value)) {
return value.join(',');
}
return value;
}
isValidInput(_input: string): boolean {
return true;
}
}
@observer
export default class ObserverTextField extends TextField<TextFieldProps> {
static defaultProps = TextField.defaultProps;
}

Опубликовать ( 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