import React from 'react'; import Layout from '../../../layout'; import Utils from '../../../nodes/controls/utils'; import { Noodl, Slot } from '../../../types'; //this stops a text field from being unfocused by the clickHandler in the viewer that handles focus globally. //The specific case is when a mouseDown is registered in the input, but the mouseUp is outside. //It'll trigger a focus change that'll blur the input field, which is annyoing when you're selecting text function preventGlobalFocusChange(e) { e.stopPropagation(); window.removeEventListener('click', preventGlobalFocusChange, true); } export interface TextInputProps extends Noodl.ReactProps { id: string; type: 'text' | 'textArea' | 'email' | 'number' | 'password' | 'url'; textStyle: Noodl.TextStyle; enabled: boolean; placeholder: string; maxLength: number; startValue: string; value: string; useLabel: boolean; label: string; labelSpacing: string; labeltextStyle: Noodl.TextStyle; useIcon: boolean; iconPlacement: 'left' | 'right'; iconSpacing: string; iconSourceType: 'image' | 'icon'; iconImageSource: Noodl.Image; iconIconSource: Noodl.Icon; iconSize: string; iconColor: Noodl.Color; onTextChanged?: (value: string) => void; onEnter?: () => void; children: Slot; } // Based on (HTMLTextAreaElement | HTMLInputElement) type InputRef = (HTMLTextAreaElement | HTMLInputElement) & { noodlNode?: Noodl.ReactProps['noodlNode']; }; type State = { value: string; }; export class TextInput extends React.Component { ref: React.MutableRefObject; constructor(props: TextInputProps) { super(props); this.state = { value: props.startValue } satisfies State; this.ref = React.createRef(); } setText(value: string) { this.setState({ value }); this.props.onTextChanged && this.props.onTextChanged(value); } componentDidMount() { //plumbing for the focused signals this.ref.current.noodlNode = this.props.noodlNode; this.setText(this.props.startValue); } render() { const style: React.CSSProperties = { ...this.props.style }; Layout.size(style, this.props); Layout.align(style, this.props); if (style.opacity === 0) { style.pointerEvents = 'none'; } const { height, ...otherStylesTmp } = style; // otherStylesTmp is not React.CSSProperties, reassigning it will correct the type. const otherStyles: React.CSSProperties = otherStylesTmp; const props = this.props; const _renderIcon = () => { if (props.iconSourceType === 'image' && props.iconImageSource !== undefined) return ( this.focus()} /> ); else if (props.iconSourceType === 'icon' && props.iconIconSource !== undefined) { const style: React.CSSProperties = { userSelect: 'none', fontSize: props.iconSize, color: props.iconColor }; if (props.iconPlacement === 'left' || props.iconPlacement === undefined) style.marginRight = props.iconSpacing; else style.marginLeft = props.iconSpacing; if (props.iconIconSource.codeAsClass === true) { return ( ); } else { return ( {props.iconIconSource.code} ); } } return null; }; let className = 'ndl-controls-textinput ' + props.id; if (props.className) className = className + ' ' + props.className; let inputContent; const inputStyles: React.CSSProperties = { ...props.textStyle, ...props.styles.input, width: '100%', height: '100%' }; inputStyles.color = props.noodlNode.context.styles.resolveColor(inputStyles.color); const inputProps = { id: props.id, value: this.state.value, ...Utils.controlEvents(props), disabled: !props.enabled, style: inputStyles, className, placeholder: props.placeholder, maxLength: props.maxLength, onChange: (e) => this.onChange(e) }; if (props.type !== 'textArea') { inputContent = ( (this.ref.current = ref)} type={this.props.type} {...inputProps} onKeyDown={(e) => this.onKeyDown(e)} onMouseDown={() => window.addEventListener('click', preventGlobalFocusChange, true)} noodl-style-tag="input" /> ); } else { inputProps.style.resize = 'none'; //disable user resizing inputContent = (