diff --git a/packages/zarm/package.json b/packages/zarm/package.json index e498208e6..8bde211d9 100644 --- a/packages/zarm/package.json +++ b/packages/zarm/package.json @@ -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", diff --git a/packages/zarm/src/__tests__/index.test.ts b/packages/zarm/src/__tests__/index.test.ts index 72f466ba7..b7eb5751b 100644 --- a/packages/zarm/src/__tests__/index.test.ts +++ b/packages/zarm/src/__tests__/index.test.ts @@ -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], @@ -402,9 +398,7 @@ describe('index', () => { "$$typeof": Symbol(react.forward_ref), "defaultProps": Object { "clearable": true, - "disabled": false, "shape": "radius", - "showCancel": false, }, "render": [Function], }, diff --git a/packages/zarm/src/custom-input/CustomInput.tsx b/packages/zarm/src/custom-input/CustomInput.tsx index ae76619f7..3988624f5 100644 --- a/packages/zarm/src/custom-input/CustomInput.tsx +++ b/packages/zarm/src/custom-input/CustomInput.tsx @@ -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'; @@ -28,7 +28,12 @@ export type CustomInputProps = BaseCustomInputProps & HTMLProps & Omit, 'type' | 'onChange' | 'onFocus' | 'onBlur'>; -const CustomInput = React.forwardRef((props, ref) => { +export interface CustomInputRef { + focus: () => void; + blur: () => void; +} + +const CustomInput = React.forwardRef((props, ref) => { const { type, clearable, @@ -36,10 +41,9 @@ const CustomInput = React.forwardRef((props, ref) => autoFocus, className, disabled, - value, - defaultValue, maxLength, label, + defaultValue = '', onChange, onBlur, onFocus, @@ -47,18 +51,13 @@ const CustomInput = React.forwardRef((props, ref) => ...restProps } = props; - const wrapperRef = (ref as any) || React.createRef(); const contentRef = React.useRef(null); const pickerRef = React.useRef(null); const inputRef = React.useRef(null); - const [currentValue, setCurrentValue] = React.useState(getValue({ value, defaultValue }, '')); + const [value, setValue] = useControllableValue({ ...props, defaultValue }); const [focused, setFocused] = React.useState(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 }); @@ -81,12 +80,12 @@ const CustomInput = React.forwardRef((props, ref) => setFocused(true); }, 0); - onFocus?.(currentValue); + onFocus?.(value); }; const onInputBlur = () => { setFocused(false); - onBlur?.(currentValue); + onBlur?.(value); }; const onKeyClick = (key) => { @@ -95,25 +94,24 @@ const CustomInput = React.forwardRef((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 = () => { @@ -139,19 +137,19 @@ const CustomInput = React.forwardRef((props, ref) => ); - const textRender =
{currentValue}
; + const textRender =
{value}
; const inputRender = ( -
+
{labelRender}
- {(currentValue === undefined || currentValue === '') && !readOnly && ( + {(value === undefined || value === '') && !readOnly && (
{placeholder}
)}
- {currentValue} + {value}
- +
{clearIconRender} @@ -160,7 +158,7 @@ const CustomInput = React.forwardRef((props, ref) => useClickAway([contentRef, pickerRef], onInputBlur); - React.useImperativeHandle(wrapperRef, () => ({ + React.useImperativeHandle(ref, () => ({ focus, blur, })); @@ -179,10 +177,6 @@ const CustomInput = React.forwardRef((props, ref) => }; }, [focused]); - React.useEffect(() => { - setCurrentValue(getValue({ value, defaultValue }, '')); - }, [value, defaultValue]); - React.useEffect(() => { if (readOnly) return; @@ -191,7 +185,7 @@ const CustomInput = React.forwardRef((props, ref) => } else { scrollToStart(); } - }, [readOnly, focused, currentValue]); + }, [readOnly, focused, value]); return readOnly ? textRender : inputRender; }); @@ -200,10 +194,6 @@ CustomInput.displayName = 'CustomInput'; CustomInput.defaultProps = { type: 'number', - disabled: false, - autoFocus: false, - readOnly: false, - clearable: false, }; export default CustomInput; diff --git a/packages/zarm/src/custom-input/__tests__/index.test.tsx b/packages/zarm/src/custom-input/__tests__/index.test.tsx index 36857b055..7b4813e09 100644 --- a/packages/zarm/src/custom-input/__tests__/index.test.tsx +++ b/packages/zarm/src/custom-input/__tests__/index.test.tsx @@ -22,7 +22,13 @@ describe('CustomInput', () => { it('show clear', () => { const onChange = jest.fn(); const { container } = render( - , + , ); const clear = container.querySelector('.za-custom-input__clear'); fireEvent.click(clear!); diff --git a/packages/zarm/src/index.ts b/packages/zarm/src/index.ts index 2c8670ae7..7580fb4e4 100644 --- a/packages/zarm/src/index.ts +++ b/packages/zarm/src/index.ts @@ -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'; @@ -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'; diff --git a/packages/zarm/src/input/Input.tsx b/packages/zarm/src/input/Input.tsx index 1d0ed991c..7f1fe7cf9 100644 --- a/packages/zarm/src/input/Input.tsx +++ b/packages/zarm/src/input/Input.tsx @@ -1,10 +1,11 @@ -import * as React from 'react'; import { createBEM } from '@zarm-design/bem'; import { CloseCircleFill } from '@zarm-design/icons'; -import { getValue } from './utils'; +import * as React from 'react'; import { ConfigContext } from '../config-provider'; -import type { BaseInputTextProps, BaseInputTextareaProps } from './interface'; +import { useControllableEventValue } from '../utils/hooks'; +import { resolveOnChange } from '../utils/resolveOnChange'; import type { HTMLProps } from '../utils/utilityTypes'; +import type { BaseInputTextareaProps, BaseInputTextProps } from './interface'; const regexAstralSymbols = /[\uD800-\uDBFF][\uDC00-\uDFFF]|\n/g; @@ -39,6 +40,13 @@ export type InputProps = { type?: string; } & (InputTextProps | InputTextareaProps); +export interface InputRef { + focus: () => void; + blur: () => void; + clear: () => void; + nativeElement: HTMLInputElement | HTMLTextAreaElement | null; +} + const Input = React.forwardRef((props, ref) => { const { type, @@ -51,10 +59,9 @@ const Input = React.forwardRef((props, ref) => { rows, className, style, - value, - defaultValue, maxLength, label, + defaultValue = '', onChange, onBlur, onFocus, @@ -64,20 +71,14 @@ const Input = React.forwardRef((props, ref) => { const wrapperRef = (ref as any) || React.createRef(); const inputRef = React.useRef(); - const [currentValue, setCurrentValue] = React.useState(getValue({ value, defaultValue }, '')); + const [value, setValue] = useControllableEventValue({ ...props, defaultValue }); const [focused, setFocused] = React.useState(autoFocus!); - const isTextarea = type === 'text' && 'rows' in props; let blurFromClear = false; let blurTimeout: number; const showClearIcon = - clearable && - !readOnly && - !disabled && - typeof value !== 'undefined' && - typeof onChange !== 'undefined' && - currentValue.length > 0; + clearable && !readOnly && !disabled && typeof value !== 'undefined' && value?.length > 0; const { prefixCls } = React.useContext(ConfigContext); const bem = createBEM('input', { prefixCls }); @@ -101,6 +102,10 @@ const Input = React.forwardRef((props, ref) => { inputRef.current!.blur(); }; + const clear = () => { + onInputClear(new Event('click')); + }; + const onInputBlur = (e) => { blurTimeout = window.setTimeout(() => { if (!blurFromClear && document.activeElement !== inputRef.current) { @@ -118,34 +123,18 @@ const Input = React.forwardRef((props, ref) => { }; const onInputChange = (e) => { - setCurrentValue(e.target.value); - onChange?.(e); + setValue(e); }; const onInputClear = (e) => { blurFromClear = true; - setCurrentValue(''); + resolveOnChange(inputRef.current, e, setValue); focus(); - - if (typeof onChange !== 'function') { - return; - } - - const event = Object.create(e); - const target = inputRef.current!; - const originalValue = target.value; - - event.target = target; - event.currentTarget = target; - target.value = ''; - onChange(event); - // reset target ref value - target.value = originalValue; }; // 渲染文字长度 const textLengthRender = showLength && maxLength && ( -
{`${countSymbols(currentValue)}/${maxLength}`}
+
{`${countSymbols(value)}/${maxLength}`}
); const commonProps: InputTextProps & InputTextareaProps = { @@ -154,18 +143,12 @@ const Input = React.forwardRef((props, ref) => { disabled, autoFocus, readOnly, - defaultValue, + value, onFocus: onInputFocus, onBlur: onInputBlur, + onChange: onInputChange, }; - if ('value' in props) { - commonProps.value = currentValue; - } - if ('onChange' in props) { - commonProps.onChange = onInputChange; - } - // 渲染输入框 const inputRender = isTextarea ? (
@@ -185,7 +168,7 @@ const Input = React.forwardRef((props, ref) => { ); // 渲染文本内容 - const textRender =
{currentValue}
; + const textRender =
{value}
; // 渲染标签栏 const labelRender = !!label &&
{label}
; @@ -198,12 +181,12 @@ const Input = React.forwardRef((props, ref) => { React.useImperativeHandle(wrapperRef, () => ({ focus, blur, + clear, + get nativeElement() { + return inputRef.current; + }, })); - React.useEffect(() => { - setCurrentValue(getValue({ value, defaultValue })); - }, [value, defaultValue]); - React.useEffect(() => { if (!autoHeight) return; diff --git a/packages/zarm/src/input/__tests__/index.test.tsx b/packages/zarm/src/input/__tests__/index.test.tsx index 02c8fad96..ad04f2313 100644 --- a/packages/zarm/src/input/__tests__/index.test.tsx +++ b/packages/zarm/src/input/__tests__/index.test.tsx @@ -1,5 +1,5 @@ -import React from 'react'; import { fireEvent, render } from '@testing-library/react'; +import React from 'react'; import Input from '../index'; describe('Input', () => { @@ -20,10 +20,11 @@ describe('Input', () => { it('renders onClear called correctly', () => { const onChange = jest.fn(); - const { container } = render(); + const { container } = render(); const input = container.querySelector('input') as HTMLInputElement; fireEvent.change(input, { target: { value: 'My new value' } }); + expect(input.value).toEqual('My new value'); const clearBtn = container.querySelector('.za-input__clear') as HTMLDivElement; fireEvent.click(clearBtn); expect(onChange).toHaveBeenCalled(); @@ -48,11 +49,12 @@ describe('Input.Base', () => { }); it('showLength', () => { - const { container } = render(); + const { container } = render( + , + ); const content = container.querySelector('.za-input__length'); expect(content?.textContent).toEqual('3/100'); }); - }); describe('Input.Textarea', () => { @@ -62,7 +64,7 @@ describe('Input.Textarea', () => { }); it('autoHeight', () => { - const wrapper = render(); + const wrapper = render(); expect(wrapper.asFragment()).toMatchSnapshot(); }); diff --git a/packages/zarm/src/input/demo.md b/packages/zarm/src/input/demo.md index bb65c7ab1..74eed1b8e 100644 --- a/packages/zarm/src/input/demo.md +++ b/packages/zarm/src/input/demo.md @@ -19,8 +19,9 @@ const Demo = () => { placeholder="请输入" value={title} onChange={(e) => { - setTitle(e.target.value); - console.log(`onChange: ${e.target.value}`); + const val = e.target.value; + setTitle(val); + console.log(`onChange: ${val}`); }} /> @@ -87,7 +88,9 @@ ReactDOM.render(, mountNode); import { useState } from 'react'; import { Input, List, Icon } from 'zarm'; -const PreviewIcon = Icon.createFromIconfont('//lf1-cdn-tos.bytegoofy.com/obj/iconpark/svg_20337_14.627ee457cf7594fbbce6d5e14b8c29ef.js'); +const PreviewIcon = Icon.createFromIconfont( + '//lf1-cdn-tos.bytegoofy.com/obj/iconpark/svg_20337_14.627ee457cf7594fbbce6d5e14b8c29ef.js', +); const Demo = () => { const [password, setPassword] = useState(''); @@ -98,9 +101,12 @@ const Demo = () => { setVisible(!visible)} /> - : setVisible(!visible)} /> + suffix={ + visible ? ( + setVisible(!visible)} /> + ) : ( + setVisible(!visible)} /> + ) } > ((props, ref) => { placeholder, showCancel, cancelText, - defaultValue, - value, + defaultValue = '', onFocus, onBlur, onChange, @@ -50,9 +50,9 @@ const SearchBar = React.forwardRef((props, ref) => { ...restProps } = props; const cancelRef = React.useRef(null); - const inputRef = React.useRef(); + const inputRef = React.useRef(); const formRef = React.createRef(); - const [currentValue, setCurrentValue] = React.useState(getValue({ value, defaultValue }, '')); + const [value, setValue] = useControllableEventValue({ ...props, defaultValue }); const [isFocus, setIsFocus] = React.useState(false); const { prefixCls, locale: globalLocal } = React.useContext(ConfigContext); @@ -61,7 +61,7 @@ const SearchBar = React.forwardRef((props, ref) => { const isShowCancel = React.useMemo(() => { if (isFunction(showCancel)) { - return showCancel(isFocus, currentValue); + return showCancel(isFocus, value); } return showCancel && isFocus; }, [showCancel, isFocus]); @@ -85,18 +85,18 @@ const SearchBar = React.forwardRef((props, ref) => { }; const onInputChange = (e: React.ChangeEvent) => { - setCurrentValue(e.target.value); - onChange?.(e); + setValue?.(e); }; const onFormSubmit = (e?: React.FormEvent) => { e?.preventDefault(); inputRef.current && inputRef.current.blur(); - onSubmit?.(currentValue); + onSubmit?.(value); }; - const onClickCancelButton = (): void => { - setCurrentValue(''); + const onClickCancelButton = (e): void => { + resolveOnChange(inputRef.current?.nativeElement, e, setValue); + inputRef.current && inputRef.current.blur(); onCancel?.(); }; @@ -107,10 +107,6 @@ const SearchBar = React.forwardRef((props, ref) => { submit: onFormSubmit, })); - React.useEffect(() => { - setCurrentValue(getValue({ value, defaultValue }, '')); - }, [defaultValue, value]); - const renderCancel = () => { return ( isShowCancel && ( @@ -124,19 +120,13 @@ const SearchBar = React.forwardRef((props, ref) => { const inputProps: InputTextProps = { type: 'search', placeholder: placeholder || (locale && locale.placeholder), - defaultValue, + value, onFocus: onInputFocus, onBlur: onInputBlur, + onChange: onInputChange, ...restProps, }; - if ('value' in props) { - inputProps.value = currentValue; - } - if ('onChange' in props) { - inputProps.onChange = onInputChange; - } - return (
@@ -152,8 +142,6 @@ const SearchBar = React.forwardRef((props, ref) => { SearchBar.defaultProps = { shape: 'radius', - disabled: false, - showCancel: false, clearable: true, }; diff --git a/packages/zarm/src/search-bar/__tests__/__snapshots__/index.test.tsx.snap b/packages/zarm/src/search-bar/__tests__/__snapshots__/index.test.tsx.snap index d0c377b5d..b7dbe88d6 100644 --- a/packages/zarm/src/search-bar/__tests__/__snapshots__/index.test.tsx.snap +++ b/packages/zarm/src/search-bar/__tests__/__snapshots__/index.test.tsx.snap @@ -82,13 +82,30 @@ exports[`SearchBar snapshot renders defaultValue correctly 1`] = `
+ + +
diff --git a/packages/zarm/src/utils/__tests__/useControllableValue.test.ts b/packages/zarm/src/utils/__tests__/useControllableValue.test.ts deleted file mode 100644 index 727877cdb..000000000 --- a/packages/zarm/src/utils/__tests__/useControllableValue.test.ts +++ /dev/null @@ -1,20 +0,0 @@ -import { renderHook } from '@testing-library/react-hooks'; -import useControllableValue from '../hooks/useControllableValue'; - -describe('useUpdateEffect', () => { - const render = (props, options?: any): any => renderHook(() => useControllableValue(props, options)); - it('defaultValue', async () => { - const hook = render({ defaultValue: 1 }); - expect(hook.result.current[0]).toEqual(1); - }); - - it('state update', () => { - const props: any = { - value: 0, - }; - const { result, rerender } = render(props); - props.value = 1; - rerender(props); - expect(result.current[0]).toEqual(1); - }); -}); \ No newline at end of file diff --git a/packages/zarm/src/utils/__tests__/useUpdateEffect.test.ts b/packages/zarm/src/utils/__tests__/useUpdateEffect.test.ts deleted file mode 100644 index 153304e86..000000000 --- a/packages/zarm/src/utils/__tests__/useUpdateEffect.test.ts +++ /dev/null @@ -1,16 +0,0 @@ -import { renderHook } from '@testing-library/react-hooks'; -import { useUpdateEffect } from '../hooks'; - -describe('useUpdateEffect', () => { - it('test on mounted', async () => { - let mountedState = 0; - const hook = renderHook(() => - useUpdateEffect(() => { - mountedState = 1; - }), - ); - expect(mountedState).toEqual(0); - hook.rerender(); - expect(mountedState).toEqual(1); - }); -}); diff --git a/packages/zarm/src/utils/hooks/index.ts b/packages/zarm/src/utils/hooks/index.ts index 2f3dba63f..bf83173a2 100644 --- a/packages/zarm/src/utils/hooks/index.ts +++ b/packages/zarm/src/utils/hooks/index.ts @@ -1,9 +1,8 @@ +export { default as useControllableEventValue } from './useControllableEventValue'; export { default as useEventCallback } from './useEventCallback'; -export { default as usePrevious } from './usePrevious'; -export { default as useSafeState } from './useSafeState'; +export { default as useLatest } from './useLatest'; export { default as useLockScroll } from './useLockScroll'; -export { default as useUpdateEffect } from './useUpdateEffect'; -export { default as useControllableValue } from './useControllableValue'; -export { default as useSafeLayoutEffect } from './useSafeLayoutEffect'; export { default as useMutationObserverRef } from './useMutationObserverRef'; -export { default as useLatest } from './useLatest'; +export { default as usePrevious } from './usePrevious'; +export { default as useSafeLayoutEffect } from './useSafeLayoutEffect'; +export { default as useSafeState } from './useSafeState'; diff --git a/packages/zarm/src/utils/hooks/useControllableEventValue/index.ts b/packages/zarm/src/utils/hooks/useControllableEventValue/index.ts new file mode 100644 index 000000000..af589def6 --- /dev/null +++ b/packages/zarm/src/utils/hooks/useControllableEventValue/index.ts @@ -0,0 +1,72 @@ +/* eslint-disable no-prototype-builtins */ +import { useMemoizedFn, useUpdate } from 'ahooks'; +import { useMemo, useRef } from 'react'; + +export interface Options { + defaultValue?: T; + defaultValuePropName?: string; + valuePropName?: string; + trigger?: string; +} + +export type Props = Record; + +export type ChangeEvent = + | React.ChangeEvent + | React.ChangeEvent; + +export interface StandardProps { + value: T; + defaultValue?: T; + onChange: (event: ChangeEvent) => void; +} + +function useControllableEventValue( + props: StandardProps, +): [T, (event: ChangeEvent) => void]; +function useControllableEventValue( + props?: Props, + options?: Options, +): [T, (event: ChangeEvent) => void]; +function useControllableEventValue(props: Props = {}, options: Options = {}) { + const { + defaultValue, + defaultValuePropName = 'defaultValue', + valuePropName = 'value', + trigger = 'onChange', + } = options; + + const value = props[valuePropName] as T; + const isControlled = props.hasOwnProperty(valuePropName); + + const initialValue = useMemo(() => { + if (isControlled) { + return value; + } + if (props.hasOwnProperty(defaultValuePropName)) { + return props[defaultValuePropName]; + } + return defaultValue; + }, []); + + const stateRef = useRef(initialValue); + if (isControlled) { + stateRef.current = value; + } + + const update = useUpdate(); + + function setState(event: ChangeEvent) { + if (!isControlled) { + stateRef.current = event.target[valuePropName]; + update(); + } + if (props[trigger]) { + props[trigger](event); + } + } + + return [stateRef.current, useMemoizedFn(setState)] as const; +} + +export default useControllableEventValue; diff --git a/packages/zarm/src/utils/hooks/useControllableValue/index.ts b/packages/zarm/src/utils/hooks/useControllableValue/index.ts deleted file mode 100644 index 60cac6139..000000000 --- a/packages/zarm/src/utils/hooks/useControllableValue/index.ts +++ /dev/null @@ -1,66 +0,0 @@ -import * as React from 'react'; -import useUpdateEffect from '../useUpdateEffect'; - -export interface Options { - defaultValue?: T; - defaultValuePropName?: string; - valuePropName?: string; - trigger?: string; -} - -export interface Props { - [key: string]: any; -} - -interface StandardProps { - value: T; - defaultValue?: T; - onChange: (val: T) => void; -} -function useControllableValue(props: StandardProps): [T, (val: T) => void]; -function useControllableValue( - props?: Props, - options?: Options, -): [T, (v: T, ...args: any[]) => void]; -function useControllableValue(props: Props = {}, options: Options = {}) { - const { - defaultValue, - defaultValuePropName = 'defaultValue', - valuePropName = 'value', - trigger = 'onChange', - } = options; - - const value = props[valuePropName] as T; - - const [state, setState] = React.useState(() => { - if (valuePropName in props) { - return value; - } - if (defaultValuePropName in props) { - return props[defaultValuePropName]; - } - return defaultValue; - }); - - useUpdateEffect(() => { - if (valuePropName in props) { - setState(value); - } - }, [value, valuePropName]); - - const handleSetState = React.useCallback( - (v: T, ...args: any[]) => { - if (!(valuePropName in props)) { - setState(v); - } - if (props[trigger]) { - props[trigger](v, ...args); - } - }, - [props, valuePropName, trigger], - ); - - return [valuePropName in props ? value : state, handleSetState] as const; -} - -export default useControllableValue; diff --git a/packages/zarm/src/utils/hooks/useUpdateEffect/index.ts b/packages/zarm/src/utils/hooks/useUpdateEffect/index.ts deleted file mode 100644 index 7cefd3435..000000000 --- a/packages/zarm/src/utils/hooks/useUpdateEffect/index.ts +++ /dev/null @@ -1,15 +0,0 @@ -import * as React from 'react'; - -const useUpdateEffect: typeof React.useEffect = (effect, deps) => { - const mounted = React.useRef(false); - - React.useEffect(() => { - if (!mounted.current) { - mounted.current = true; - } else { - return effect(); - } - }, deps); -}; - -export default useUpdateEffect; diff --git a/packages/zarm/src/utils/resolveOnChange.ts b/packages/zarm/src/utils/resolveOnChange.ts new file mode 100644 index 000000000..651573db7 --- /dev/null +++ b/packages/zarm/src/utils/resolveOnChange.ts @@ -0,0 +1,40 @@ +export function resolveOnChange( + target: E, + e: + | React.ChangeEvent + | React.MouseEvent + | React.CompositionEvent, + onChange: undefined | ((event: React.ChangeEvent) => void), + targetValue?: string, +) { + if (!onChange) { + return; + } + let event = e; + + if (e.type === 'click') { + const currentTarget = target.cloneNode(true) as E; + + // click clear icon + event = Object.create(e, { + target: { value: currentTarget }, + currentTarget: { value: currentTarget }, + }); + + currentTarget.value = ''; + onChange(event as React.ChangeEvent); + return; + } + + if (targetValue !== undefined) { + event = Object.create(e, { + target: { value: target }, + currentTarget: { value: target }, + }); + + target.value = targetValue; + onChange(event as React.ChangeEvent); + return; + } + onChange(event as React.ChangeEvent); +} diff --git a/yarn.lock b/yarn.lock index 848a75fef..702b84257 100644 --- a/yarn.lock +++ b/yarn.lock @@ -3942,6 +3942,11 @@ jest-diff "^26.0.0" pretty-format "^26.0.0" +"@types/js-cookie@^2.x.x": + version "2.2.7" + resolved "https://registry.yarnpkg.com/@types%2fjs-cookie/-/js-cookie-2.2.7.tgz#226a9e31680835a6188e887f3988e60c04d3f6a3" + integrity sha1-ImqeMWgINaYYjoh/OYjmDATT9qM= + "@types/json-schema@*", "@types/json-schema@^7.0.4", "@types/json-schema@^7.0.5": version "7.0.7" resolved "https://registry.yarnpkg.com/@types/json-schema/-/json-schema-7.0.7.tgz#98a993516c859eb0d5c4c8f098317a9ea68db9ad" @@ -4041,7 +4046,7 @@ "@types/react-dom@18.0.9", "@types/react-dom@>=16.9.0", "@types/react-dom@^17.0.3", "@types/react-dom@^18.0.9": version "18.0.9" - resolved "https://registry.yarnpkg.com/@types/react-dom/-/react-dom-18.0.9.tgz#ffee5e4bfc2a2f8774b15496474f8e7fe8d0b504" + resolved "https://registry.yarnpkg.com/@types%2freact-dom/-/react-dom-18.0.9.tgz#ffee5e4bfc2a2f8774b15496474f8e7fe8d0b504" integrity sha512-qnVvHxASt/H7i+XG1U1xMiY5t+IHcPGUK7TDMDzom08xa7e86eCeKOiLZezwCKVxJn6NEiiy2ekgX8aQssjIKg== dependencies: "@types/react" "*" @@ -4062,7 +4067,7 @@ "@types/react@*", "@types/react@18.0.26", "@types/react@>=16.9.0", "@types/react@^17.0.3", "@types/react@^18.0.26": version "18.0.26" - resolved "https://registry.yarnpkg.com/@types/react/-/react-18.0.26.tgz#8ad59fc01fef8eaf5c74f4ea392621749f0b7917" + resolved "https://registry.yarnpkg.com/@types%2freact/-/react-18.0.26.tgz#8ad59fc01fef8eaf5c74f4ea392621749f0b7917" integrity sha512-hCR3PJQsAIXyxhTNSiDFY//LhnMZWpNNr5etoCqx/iUfGc5gXWtQR2Phl908jVR6uPXacojQWTg4qRpkxTuGug== dependencies: "@types/prop-types" "*" @@ -4582,6 +4587,27 @@ aggregate-error@^3.0.0: clean-stack "^2.0.0" indent-string "^4.0.0" +ahooks-v3-count@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/ahooks-v3-count/-/ahooks-v3-count-1.0.0.tgz#ddeb392e009ad6e748905b3cbf63a9fd8262ca80" + integrity sha512-V7uUvAwnimu6eh/PED4mCDjE7tokeZQLKlxg9lCTMPhN+NjsSbtdacByVlR1oluXQzD3MOw55wylDmQo4+S9ZQ== + +ahooks@^3.7.6: + version "3.7.6" + resolved "https://registry.yarnpkg.com/ahooks/-/ahooks-3.7.6.tgz#4fdbe3be421775844bd64ab17c9c170424675247" + integrity sha512-p+2j4H/BI9vqXR0fZI7S/q6fUPxPklQnHqvU7zAVBljMFNSFeYRWB2iHHbjpXGOwUTOBYCh2OuvIHyJYj6Lpag== + dependencies: + "@babel/runtime" "^7.21.0" + "@types/js-cookie" "^2.x.x" + ahooks-v3-count "^1.0.0" + dayjs "^1.9.1" + intersection-observer "^0.12.0" + js-cookie "^2.x.x" + lodash "^4.17.21" + resize-observer-polyfill "^1.5.1" + screenfull "^5.0.0" + tslib "^2.4.1" + ajv-formats@^2.1.1: version "2.1.1" resolved "https://registry.yarnpkg.com/ajv-formats/-/ajv-formats-2.1.1.tgz#6e669400659eb74973bbf2e33327180a0996b520" @@ -7265,7 +7291,7 @@ dateformat@^3.0.0: resolved "https://registry.yarnpkg.com/dateformat/-/dateformat-3.0.3.tgz#a6e37499a4d9a9cf85ef5872044d62901c9889ae" integrity sha512-jyCETtSl3VMZMWeRo7iY1FL19ges1t55hMo5yaam4Jrsm5EPL89UQkoQRyiI+Yf4k8r2ZpdngkV8hr1lIdjb3Q== -dayjs@^1.11.1: +dayjs@^1.11.1, dayjs@^1.9.1: version "1.11.7" resolved "https://registry.npmjs.org/dayjs/-/dayjs-1.11.7.tgz#4b296922642f70999544d1144a2c25730fce63e2" integrity sha512-+Yw9U6YO5TQohxLcIkrXBeY73WP3ejHWVvx8XCk3gxvQDCTEmS48ZrSZCKciI7Bhl/uCMyxYtE9UqRILmFphkQ== @@ -10430,7 +10456,7 @@ interpret@^3.1.1: resolved "https://registry.yarnpkg.com/interpret/-/interpret-3.1.1.tgz#5be0ceed67ca79c6c4bc5cf0d7ee843dcea110c4" integrity sha512-6xwYfHbajpoF0xLW+iwLkhwgvLoZDfjYfoFNu8ftMoXINzwuymNLd9u/KmwtdT2GbR+/Cz66otEGEVVUHX9QLQ== -intersection-observer@^0.12.2: +intersection-observer@^0.12.0, intersection-observer@^0.12.2: version "0.12.2" resolved "https://registry.yarnpkg.com/intersection-observer/-/intersection-observer-0.12.2.tgz#4a45349cc0cd91916682b1f44c28d7ec737dc375" integrity sha512-7m1vEcPCxXYI8HqnL8CKI6siDyD+eIWSwgB3DZA+ZTogxk9I4CDnj4wilt9x/+/QbHI4YG5YZNmC6458/e9Ktg== @@ -11564,6 +11590,11 @@ jetifier@^1.6.2: resolved "https://registry.yarnpkg.com/jetifier/-/jetifier-1.6.6.tgz#fec8bff76121444c12dc38d2dad6767c421dab68" integrity sha512-JNAkmPeB/GS2tCRqUzRPsTOHpGDah7xP18vGJfIjZC+W2sxEHbxgJxetIjIqhjQ3yYbYNEELkM/spKLtwoOSUQ== +js-cookie@^2.x.x: + version "2.2.1" + resolved "https://registry.yarnpkg.com/js-cookie/-/js-cookie-2.2.1.tgz#69e106dc5d5806894562902aa5baec3744e9b2b8" + integrity sha1-aeEG3F1YBolFYpAqpbrsN0Tpsrg= + "js-tokens@^3.0.0 || ^4.0.0", js-tokens@^4.0.0: version "4.0.0" resolved "https://registry.yarnpkg.com/js-tokens/-/js-tokens-4.0.0.tgz#19203fb59991df98e3a287050d4647cdeaf32499" @@ -16785,6 +16816,11 @@ schema-utils@^4.0.0: ajv-formats "^2.1.1" ajv-keywords "^5.0.0" +screenfull@^5.0.0: + version "5.2.0" + resolved "https://registry.yarnpkg.com/screenfull/-/screenfull-5.2.0.tgz#6533d524d30621fc1283b9692146f3f13a93d1ba" + integrity sha1-ZTPVJNMGIfwSg7lpIUbz8TqT0bo= + scroll-into-view-if-needed@^3.0.3: version "3.0.3" resolved "https://registry.npmjs.org/scroll-into-view-if-needed/-/scroll-into-view-if-needed-3.0.3.tgz#57256bef78f3c3c288070d2aaa63cf547aa11e70" @@ -18562,6 +18598,11 @@ tslib@^2.0.3, tslib@^2.1.0: resolved "https://registry.yarnpkg.com/tslib/-/tslib-2.2.0.tgz#fb2c475977e35e241311ede2693cee1ec6698f5c" integrity sha512-gS9GVHRU+RGn5KQM2rllAlR3dU6m7AcpJKdtH8gFvQiC4Otgk98XnmMU+nZenHt/+VhnBPWwgrJsyrdcw6i23w== +tslib@^2.4.1: + version "2.5.0" + resolved "https://registry.yarnpkg.com/tslib/-/tslib-2.5.0.tgz#42bfed86f5787aeb41d031866c8f402429e0fddf" + integrity sha512-336iVw3rtn2BUK7ORdIAHTyxHGRIHVReokCR3XjbckJMK7ms8FysBfhLR8IXnAgy7T0PTPNBWKiH514FOW/WSg== + tsutils@^3.21.0: version "3.21.0" resolved "https://registry.yarnpkg.com/tsutils/-/tsutils-3.21.0.tgz#b48717d394cea6c1e096983eed58e9d61715b623"