import { Children, cloneElement, CSSProperties, ReactElement, ReactNode, useEffect, useMemo, useRef, useState, } from 'react' import classNames from 'classnames' import { CommonComponentProps } from '../../utils/types' import { shuffle } from '../../utils' import { Icon } from '../icon/Icon' import { Button, ButtonProps } from '../button/Button' import { Popup, PopupProps } from '../popup/Popup' import './NumberKeyboard.scss' import { useEvent } from '../../use' export interface NumberKeyboardProps extends CommonComponentProps { className?: string style?: CSSProperties children?: ReactElement title?: ReactNode cancelText?: ReactNode cancelProps?: ButtonProps confirmText?: ReactNode confirmProps?: ButtonProps value?: string defaultValue?: string onKeyClick?: (key: string) => void onChange?: (value: string) => void onDelete?: () => void visible?: boolean defaultVisible?: boolean onVisible?: (visible: boolean) => void onClose?: (visible: false) => void onCancel?: (visible: false) => void onConfirm?: (visible: false) => void extraKey?: string random?: boolean triggerProp?: string hideType?: string focusedProp?: string maxLength?: number popupProps?: PopupProps } export function NumberKeyboard(props: NumberKeyboardProps) { const { className, children, title, cancelText = '取消', cancelProps, confirmText = '完成', confirmProps, value, defaultValue, onKeyClick, onChange, onDelete, visible, defaultVisible, onVisible, onClose, onCancel, onConfirm, extraKey, random, triggerProp = 'onClick', hideType = 'click', focusedProp, maxLength = Infinity, popupProps = {}, ...restProps } = props const { placement = 'bottom', mask = false, lockScroll = false, ...restPopupProps } = popupProps const [innerValue, setInnerValue] = useState(value ?? defaultValue ?? '') // 受控 useEffect(() => { if (value != null) { setInnerValue(value) } }, [value]) const handleKeyClick = (key: string) => { if (innerValue.length >= maxLength) { return } const nextValue = innerValue + key // 非受控 if (value == null) { setInnerValue(nextValue) } onChange?.(nextValue) onKeyClick?.(key) } const handleDelete = () => { const nextValue = innerValue.slice(0, -1) // 非受控 if (value == null) { setInnerValue(nextValue) } onChange?.(nextValue) onDelete?.() } const [innerVisible, setInnerVisible] = useState( visible ?? defaultVisible ?? false ) const keyboardRef = useRef(null) const setVisible = (show: boolean) => { // visible 非受控 if (visible == null) { setInnerVisible(show) } onVisible?.(show) } // visible 受控 useEffect(() => { if (visible != null) { setInnerVisible(visible) } }, [visible]) const handleCancel = useEvent(() => { setVisible(false) onCancel?.(false) onClose?.(false) }) const handleConfirm = useEvent(() => { setVisible(false) onConfirm?.(false) onClose?.(false) }) useEffect(() => { if (!innerVisible) { return } const handler = (event: Event) => { if (keyboardRef.current) { if (!keyboardRef.current.contains(event.target as HTMLElement)) { setVisible(false) } } } document.addEventListener(hideType, handler, true) return () => { document.removeEventListener(hideType, handler, true) } }, [hideType, innerVisible]) const numArray = useMemo(() => { let arr = Array(10) .fill(0) .map((_, i) => (i + 1) % 10) if (random) { shuffle(arr, true) } return arr }, [random]) const numberKeyboardClass = classNames( 's-number-keyboard', { 's-number-keyboard-has-extra': extraKey != null, }, className ) let target: ReactElement | undefined try { target = Children.only(children) } catch {} return ( <> {target && (() => { const targetProps = { ...target.props, [triggerProp]: (...args: any[]) => { setInnerVisible(true) target!.props[triggerProp]?.(...args) }, } if (focusedProp) { targetProps[focusedProp] = innerVisible } return cloneElement(target, targetProps) })()}
{title &&
{title}
}
{numArray.map((n, i) => (
handleKeyClick(n + '')} > {n}
))} {extraKey && (
handleKeyClick(extraKey + '')} > {extraKey}
)}
) } export default NumberKeyboard