/
githubmirror
/
client
Обзор
Документация
Войти
/
githubmirror
/
client
Код
Запросы
0
Пакеты
0
Релизы
0
Аналитика
Безопасность
master
shared/common-adapters/input3.tsx
352 строки
11 KB
chrisnojima
refactor(styles): theme via hooks, no android remount (#29515)
08 авг 2026, 02:24
Не верифицирован
08 авг 2026, 02:24
a41d38e
Код
Авторство
О чём код?
import * as React from 'react' import * as Styles from '@/styles' import {Box2} from './box' import IconAuto from './icon-auto' import Text from './text' import {getTextStyle} from './text.styles' import {TextInput as _TextInputReal} from 'react-native' const NativeTextInput = _TextInputReal as unknown as React.ComponentType<{autoCapitalize?: string; autoCorrect?: boolean; autoFocus?: boolean; editable?: boolean; keyboardType?: string; maxLength?: number; multiline?: boolean; onBlur?: () => void; onChangeText?: (text: string) => void; onFocus?: () => void; onSubmitEditing?: () => void; placeholder?: string; placeholderTextColor?: string; ref?: React.Ref<InputLikeRef>; returnKeyType?: string; secureTextEntry?: boolean; selectTextOnFocus?: boolean; style?: Styles.StylesCrossPlatform; submitBehavior?: 'submit' | 'blurAndSubmit' | 'newline'; textContentType?: string; underlineColorAndroid?: string; value?: string}> import type {Input3Props, Input3Ref} from './input3.shared' export type {Input3Props, Input3Ref} from './input3.shared' // Desktop only CSS import import './input.css' // Stub types to avoid DOM lib dependency in native tsconfig type InputLikeRef = { blur?: () => void focus?: () => void select?: () => void clear?: () => void value?: string } type InputChangeEvent = {target: {value: string}} // Shared container + icon/prefix/decoration frame around the platform input element const InputFrame = (p: { children: React.ReactNode focused: boolean fontSize?: number props: Input3Props }) => { const styles = useStyles() const theme = Styles.useTheme() const {children, focused, fontSize, props} = p const {containerStyle, decoration, disabled, error, hideBorder, icon, prefix} = props return ( <Box2 direction="horizontal" alignItems="center" padding="xtiny" style={Styles.collapseStyles([ styles.container, focused && styles.focused, error && styles.error, hideBorder && styles.hideBorder, disabled && styles.disabled, containerStyle, ])} > {!!icon && ( <Box2 direction="horizontal" style={styles.icon}> <IconAuto color={theme.black_20} type={icon} fontSize={fontSize} style={styles.displayFlex} /> </Box2> )} {!!prefix && <Text type="BodySemibold" style={styles.prefix}>{prefix}</Text>} {children} {decoration} </Box2> ) } type InputKeyEvent = { key?: string shiftKey?: boolean ctrlKey?: boolean altKey?: boolean } const DesktopInput3 = (props: Input3Props & {ref?: React.Ref<Input3Ref>}) => { const theme = Styles.useTheme() const styles = useStyles() const { autoCapitalize, autoCorrect, autoFocus, disabled, growAndScroll, inputStyle, maxLength, multiline, selectTextOnFocus, onBlur: onBlurProp, onChangeText, onClick, onEnterKeyDown, onFocus: onFocusProp, onKeyDown: onKeyDownProp, placeholder, ref, rowsMax, rowsMin, secureTextEntry, spellCheck, textType = 'Body', value, } = props const [focused, setFocused] = React.useState(false) const inputRef = React.useRef<InputLikeRef>(null) const isComposingRef = React.useRef(false) const onFocus = () => { if (disabled) return setFocused(true) if (selectTextOnFocus) { inputRef.current?.select?.() } onFocusProp?.() } const onBlur = () => { setFocused(false) onBlurProp?.() } const onChange = (e: InputChangeEvent) => { onChangeText?.(e.target.value) } const onKeyDown = (e: InputKeyEvent) => { if (isComposingRef.current) return onKeyDownProp?.(e as React.KeyboardEvent) if (e.key === 'Enter' && !(e.shiftKey || e.ctrlKey || e.altKey)) { onEnterKeyDown?.(e as React.KeyboardEvent) } } const isControlled = typeof value === 'string' React.useImperativeHandle(ref, () => ({ blur: () => inputRef.current?.blur?.(), clear: () => { if (isControlled) { onChangeText?.('') } else if (inputRef.current) { inputRef.current.value = '' } }, focus: () => inputRef.current?.focus?.(), })) const textStyle = getTextStyle(textType, theme) const fontSize = textStyle.fontSize const lineHeight = textStyle.lineHeight === undefined ? 20 : typeof textStyle.lineHeight === 'string' ? parseInt(textStyle.lineHeight, 10) || 20 : textStyle.lineHeight const rows = rowsMin || Math.min(2, rowsMax || 2) const commonInputProps = { autoCapitalize, autoCorrect: autoCorrect === undefined ? undefined : autoCorrect ? 'on' : 'off', autoFocus, maxLength, onBlur, onChange, onClick, onCompositionEnd: () => { isComposingRef.current = false }, onCompositionStart: () => { isComposingRef.current = true }, onFocus, onKeyDown, placeholder, ref: inputRef, spellCheck, value, ...(disabled ? {readOnly: true as const} : {}), 'data-allow-keyboard-shortcuts': 'true', } const inputElement = multiline ? ( <textarea {...(commonInputProps as unknown as React.TextareaHTMLAttributes<HTMLTextAreaElement>)} ref={inputRef as React.RefObject<HTMLTextAreaElement>} rows={rows} style={ Styles.collapseStyles([ styles.noChrome, textStyle, styles.multiline, {minHeight: rows * lineHeight}, rowsMax && {maxHeight: rowsMax * lineHeight}, growAndScroll && styles.growAndScroll, inputStyle, ]) as React.CSSProperties } /> ) : ( <input {...(commonInputProps as unknown as React.InputHTMLAttributes<HTMLInputElement>)} ref={inputRef as React.RefObject<HTMLInputElement>} type={secureTextEntry ? 'password' : 'text'} style={ Styles.collapseStyles([textStyle, styles.noChrome, styles.singleline, inputStyle]) as React.CSSProperties } /> ) return ( <InputFrame focused={focused} fontSize={fontSize} props={props}> {inputElement} </InputFrame> ) } const NativeInput3 = (props: Input3Props & {ref?: React.Ref<Input3Ref>}) => { const styles = useStyles() const theme = Styles.useTheme() const { autoCapitalize, autoCorrect, autoFocus, disabled, inputStyle, keyboardType, maxLength, multiline, onEnterKeyDown, onBlur: onBlurProp, onChangeText, onFocus: onFocusProp, placeholder, ref, returnKeyType, rowsMax, rowsMin, secureTextEntry, selectTextOnFocus, textContentType, textType = 'Body', value, } = props const [focused, setFocused] = React.useState(false) const inputRef = React.useRef<InputLikeRef>(null) const onFocus = () => { if (disabled) return setFocused(true) onFocusProp?.() } const onBlur = () => { setFocused(false) onBlurProp?.() } const isControlled = typeof value === 'string' React.useImperativeHandle(ref, () => ({ blur: () => inputRef.current?.blur?.(), clear: () => { if (isControlled) { onChangeText?.('') } else { inputRef.current?.clear?.() } }, focus: () => inputRef.current?.focus?.(), })) let textStyle = getTextStyle(textType, theme) if (isIOS) { const {lineHeight: _, ...rest} = textStyle textStyle = rest } const fontSize = textStyle.fontSize const lineHeight = getTextStyle(textType, theme).lineHeight ?? 20 const rows = rowsMin || Math.min(2, rowsMax || 2) return ( <InputFrame focused={focused} fontSize={fontSize} props={props}> <NativeTextInput autoCapitalize={autoCapitalize ?? 'none'} autoCorrect={autoCorrect ?? false} autoFocus={autoFocus} editable={!disabled} keyboardType={keyboardType ?? 'default'} maxLength={maxLength} multiline={multiline} onBlur={onBlur} onChangeText={onChangeText} onFocus={onFocus} onSubmitEditing={onEnterKeyDown} placeholder={placeholder} placeholderTextColor={theme.black_35} ref={inputRef as React.Ref<InputLikeRef>} returnKeyType={returnKeyType} secureTextEntry={secureTextEntry} selectTextOnFocus={selectTextOnFocus} style={Styles.collapseStyles([ textStyle, styles.input, multiline && styles.multiline, multiline && {minHeight: rows * lineHeight}, multiline && rowsMax && {maxHeight: rowsMax * lineHeight}, inputStyle, ])} submitBehavior={multiline ? 'newline' : 'blurAndSubmit'} textContentType={textContentType} underlineColorAndroid="transparent" value={value} /> </InputFrame> ) } function Input3(props: Input3Props & {ref?: React.Ref<Input3Ref>}) { if (!isMobile) return <DesktopInput3 {...props} /> return <NativeInput3 {...props} /> } const useStyles = Styles.createStyleHook( theme => ({ container: Styles.platformStyles({ common: { backgroundColor: theme.white, ...Styles.border(theme.black_10, 1, Styles.borderRadius), }, isElectron: { // multiline inputs use field-sizing:content, whose intrinsic width follows the // text; containment keeps a long unbreakable string from widening the layout contain: 'inline-size', width: '100%', }, }), disabled: {opacity: 0.4}, displayFlex: Styles.platformStyles({isElectron: {display: 'flex'}}), error: {borderColor: theme.red}, focused: {borderColor: theme.blue}, growAndScroll: Styles.platformStyles({ isElectron: { fieldSizing: 'fixed', maxHeight: '100%', overflowY: 'auto', scrollbarGutter: 'stable', }, }), hideBorder: {borderWidth: 0}, icon: {marginRight: Styles.globalMargins.xtiny}, input: Styles.platformStyles({ isMobile: { borderWidth: 0, flexGrow: 1, }, }), multiline: Styles.platformStyles({ isElectron: { fieldSizing: 'content', // break anywhere so an unbreakable string (long url) wraps instead of // demanding its full width overflowWrap: 'anywhere', ...Styles.paddingV(0), resize: 'none', width: '100%', }, isMobile: { height: undefined, textAlignVertical: 'top', }, }), noChrome: Styles.platformStyles({ isElectron: { borderWidth: 0, lineHeight: 'unset', outline: 'none', }, }), prefix: {marginRight: Styles.globalMargins.xtiny}, singleline: Styles.platformStyles({ isElectron: { flex: 1, minWidth: 0, width: '100%', }, }), }) as const ) export default Input3