Skip to content

Commit

Permalink
fix: input controllable value (#1086)
Browse files Browse the repository at this point in the history
  • Loading branch information
dominicleo authored Apr 11, 2023
1 parent bddc4c9 commit edabb53
Show file tree
Hide file tree
Showing 21 changed files with 299 additions and 268 deletions.
1 change: 1 addition & 0 deletions packages/zarm/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -70,6 +70,7 @@
"@use-gesture/react": "^10.1.1",
"@zarm-design/bem": "^0.0.6",
"@zarm-design/icons": "^0.1.11",
"ahooks": "^3.7.6",
"better-scroll": "2.3.1",
"classnames": "^2.3.1",
"color": "^3.1.3",
Expand Down
6 changes: 0 additions & 6 deletions packages/zarm/src/__tests__/index.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -121,10 +121,6 @@ describe('index', () => {
"CustomInput": Object {
"$$typeof": Symbol(react.forward_ref),
"defaultProps": Object {
"autoFocus": false,
"clearable": false,
"disabled": false,
"readOnly": false,
"type": "number",
},
"render": [Function],
Expand Down Expand Up @@ -402,9 +398,7 @@ describe('index', () => {
"$$typeof": Symbol(react.forward_ref),
"defaultProps": Object {
"clearable": true,
"disabled": false,
"shape": "radius",
"showCancel": false,
},
"render": [Function],
},
Expand Down
60 changes: 25 additions & 35 deletions packages/zarm/src/custom-input/CustomInput.tsx
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
import { createBEM } from '@zarm-design/bem';
import { CloseCircleFill } from '@zarm-design/icons';
import { useControllableValue } from 'ahooks';
import * as React from 'react';
import { ConfigContext } from '../config-provider';
import { getValue } from '../input/utils';
import KeyboardPicker from '../keyboard-picker';
import useClickAway from '../use-click-away';
import type { HTMLProps } from '../utils/utilityTypes';
Expand All @@ -28,37 +28,36 @@ export type CustomInputProps = BaseCustomInputProps &
HTMLProps<CustomInputCssVars> &
Omit<React.InputHTMLAttributes<HTMLInputElement>, 'type' | 'onChange' | 'onFocus' | 'onBlur'>;

const CustomInput = React.forwardRef<unknown, CustomInputProps>((props, ref) => {
export interface CustomInputRef {
focus: () => void;
blur: () => void;
}

const CustomInput = React.forwardRef<CustomInputRef, CustomInputProps>((props, ref) => {
const {
type,
clearable,
readOnly,
autoFocus,
className,
disabled,
value,
defaultValue,
maxLength,
label,
defaultValue = '',
onChange,
onBlur,
onFocus,
placeholder,
...restProps
} = props;

const wrapperRef = (ref as any) || React.createRef<HTMLDivElement>();
const contentRef = React.useRef<HTMLDivElement>(null);
const pickerRef = React.useRef<HTMLDivElement>(null);
const inputRef = React.useRef<HTMLInputElement>(null);
const [currentValue, setCurrentValue] = React.useState(getValue({ value, defaultValue }, ''));
const [value, setValue] = useControllableValue({ ...props, defaultValue });
const [focused, setFocused] = React.useState<boolean>(autoFocus!);

const showClearIcon =
clearable &&
typeof value !== 'undefined' &&
currentValue.length > 0 &&
typeof onChange === 'function';
const showClearIcon = clearable && typeof value !== 'undefined' && value?.length > 0;

const { prefixCls } = React.useContext(ConfigContext);
const bem = createBEM('custom-input', { prefixCls });
Expand All @@ -81,12 +80,12 @@ const CustomInput = React.forwardRef<unknown, CustomInputProps>((props, ref) =>
setFocused(true);
}, 0);

onFocus?.(currentValue);
onFocus?.(value);
};

const onInputBlur = () => {
setFocused(false);
onBlur?.(currentValue);
onBlur?.(value);
};

const onKeyClick = (key) => {
Expand All @@ -95,25 +94,24 @@ const CustomInput = React.forwardRef<unknown, CustomInputProps>((props, ref) =>
return;
}

if (key !== 'delete' && currentValue.length >= maxLength!) {
if (key !== 'delete' && value?.length >= maxLength!) {
return;
}

const newValue =
key === 'delete' ? currentValue.slice(0, currentValue.length - 1) : currentValue + key;
const newValue = key === 'delete' ? value?.slice(0, value?.length - 1) : value + key;

if (typeof value === 'undefined') {
setCurrentValue(newValue);
setValue(newValue);
}

onChange?.(newValue);
setValue?.(newValue);
};

const onInputClear = (e) => {
e.stopPropagation();
setCurrentValue('');
setValue('');

onChange?.('');
setValue?.('');
};

const scrollToStart = () => {
Expand All @@ -139,19 +137,19 @@ const CustomInput = React.forwardRef<unknown, CustomInputProps>((props, ref) =>
<CloseCircleFill className={bem('clear')} onClick={onInputClear} />
);

const textRender = <div className={bem('content')}>{currentValue}</div>;
const textRender = <div className={bem('content')}>{value}</div>;

const inputRender = (
<div {...restProps} ref={wrapperRef} className={cls} onClick={onInputFocus}>
<div {...restProps} className={cls} onClick={onInputFocus}>
{labelRender}
<div className={bem('content')}>
{(currentValue === undefined || currentValue === '') && !readOnly && (
{(value === undefined || value === '') && !readOnly && (
<div className={bem('placeholder')}>{placeholder}</div>
)}
<div ref={contentRef} className={bem('virtual-input')}>
{currentValue}
{value}
</div>
<input ref={inputRef} type="hidden" value={currentValue} />
<input ref={inputRef} type="hidden" value={value} />
</div>
{clearIconRender}
<KeyboardPicker ref={pickerRef} visible={focused} type={type} onKeyClick={onKeyClick} />
Expand All @@ -160,7 +158,7 @@ const CustomInput = React.forwardRef<unknown, CustomInputProps>((props, ref) =>

useClickAway([contentRef, pickerRef], onInputBlur);

React.useImperativeHandle(wrapperRef, () => ({
React.useImperativeHandle(ref, () => ({
focus,
blur,
}));
Expand All @@ -179,10 +177,6 @@ const CustomInput = React.forwardRef<unknown, CustomInputProps>((props, ref) =>
};
}, [focused]);

React.useEffect(() => {
setCurrentValue(getValue({ value, defaultValue }, ''));
}, [value, defaultValue]);

React.useEffect(() => {
if (readOnly) return;

Expand All @@ -191,7 +185,7 @@ const CustomInput = React.forwardRef<unknown, CustomInputProps>((props, ref) =>
} else {
scrollToStart();
}
}, [readOnly, focused, currentValue]);
}, [readOnly, focused, value]);

return readOnly ? textRender : inputRender;
});
Expand All @@ -200,10 +194,6 @@ CustomInput.displayName = 'CustomInput';

CustomInput.defaultProps = {
type: 'number',
disabled: false,
autoFocus: false,
readOnly: false,
clearable: false,
};

export default CustomInput;
8 changes: 7 additions & 1 deletion packages/zarm/src/custom-input/__tests__/index.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,13 @@ describe('CustomInput', () => {
it('show clear', () => {
const onChange = jest.fn();
const { container } = render(
<CustomInput clearable className="custom-input" value="test" onChange={onChange} autoFocus />,
<CustomInput
clearable
className="custom-input"
defaultValue="test"
onChange={onChange}
autoFocus
/>,
);
const clear = container.querySelector('.za-custom-input__clear');
fireEvent.click(clear!);
Expand Down
16 changes: 14 additions & 2 deletions packages/zarm/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,13 @@ export type { ImageCssVars, ImageProps } from './image';
export { default as ImagePreview } from './image-preview';
export type { ImagePreviewCssVars, ImagePreviewProps } from './image-preview';
export { default as Input } from './input';
export type { InputCssVars, InputProps, InputTextareaProps, InputTextProps } from './input';
export type {
InputCssVars,
InputProps,
InputRef,
InputTextareaProps,
InputTextProps,
} from './input';
export { default as Keyboard } from './keyboard';
export type { KeyboardCssVars, KeyboardProps } from './keyboard';
export { default as KeyboardPicker } from './keyboard-picker';
Expand All @@ -62,7 +68,13 @@ export type { MaskCssVars, MaskProps } from './mask';
export { default as Message } from './message';
export type { MessageCssVars, MessageProps } from './message';
export { default as Modal } from './modal';
export type { ModalCssVars, ModalProps, ModalShowProps, ModalAlertProps, ModalConfirmProps } from './modal';
export type {
ModalAlertProps,
ModalConfirmProps,
ModalCssVars,
ModalProps,
ModalShowProps,
} from './modal';
export { default as NavBar } from './nav-bar';
export type { NavBarCssVars, NavBarProps } from './nav-bar';
export { default as NoticeBar } from './notice-bar';
Expand Down
Loading

0 comments on commit edabb53

Please sign in to comment.