mirror of
https://github.com/fluxscape/fluxscape.git
synced 2026-01-12 07:12:54 +01:00
Initial commit
Co-Authored-By: Eric Tuvesson <eric.tuvesson@gmail.com> Co-Authored-By: mikaeltellhed <2311083+mikaeltellhed@users.noreply.github.com> Co-Authored-By: kotte <14197736+mrtamagotchi@users.noreply.github.com> Co-Authored-By: Anders Larsson <64838990+anders-topp@users.noreply.github.com> Co-Authored-By: Johan <4934465+joolsus@users.noreply.github.com> Co-Authored-By: Tore Knudsen <18231882+torekndsn@users.noreply.github.com> Co-Authored-By: victoratndl <99176179+victoratndl@users.noreply.github.com>
This commit is contained in:
@@ -0,0 +1,264 @@
|
||||
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<TextInputProps, State> {
|
||||
ref: React.MutableRefObject<InputRef>;
|
||||
|
||||
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 (
|
||||
<img
|
||||
alt=""
|
||||
src={props.iconImageSource}
|
||||
style={{
|
||||
width: props.iconSize,
|
||||
height: props.iconSize
|
||||
}}
|
||||
onClick={() => 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 (
|
||||
<span className={[props.iconIconSource.class, props.iconIconSource.code].join(' ')} style={style}></span>
|
||||
);
|
||||
} else {
|
||||
return (
|
||||
<span className={props.iconIconSource.class} style={style}>
|
||||
{props.iconIconSource.code}
|
||||
</span>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
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 = (
|
||||
<input
|
||||
ref={(ref) => (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 = (
|
||||
<textarea
|
||||
ref={(ref) => (this.ref.current = ref)}
|
||||
{...inputProps}
|
||||
onKeyDown={(e) => this.onKeyDown(e)}
|
||||
noodl-style-tag="input"
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
const inputWrapperStyle = {
|
||||
display: 'flex',
|
||||
alignItems: 'center',
|
||||
...props.styles.inputWrapper
|
||||
};
|
||||
|
||||
const heightInPercent = height && height[String(height).length - 1] === '%';
|
||||
|
||||
if (props.useLabel) {
|
||||
if (heightInPercent) {
|
||||
inputWrapperStyle.flexGrow = 1;
|
||||
} else {
|
||||
inputWrapperStyle.height = height;
|
||||
}
|
||||
} else {
|
||||
Object.assign(inputWrapperStyle, otherStyles);
|
||||
inputWrapperStyle.height = height;
|
||||
}
|
||||
|
||||
if (props.type !== 'textArea') {
|
||||
inputWrapperStyle.alignItems = 'center';
|
||||
}
|
||||
|
||||
const inputWithWrapper = (
|
||||
<div style={inputWrapperStyle} noodl-style-tag="inputWrapper">
|
||||
{props.useIcon && props.iconPlacement === 'left' ? _renderIcon() : null}
|
||||
{inputContent}
|
||||
{props.useIcon && props.iconPlacement === 'right' ? _renderIcon() : null}
|
||||
</div>
|
||||
);
|
||||
|
||||
if (props.useLabel) {
|
||||
otherStyles.display = 'flex';
|
||||
otherStyles.flexDirection = 'column';
|
||||
if (heightInPercent) otherStyles.height = height;
|
||||
|
||||
const labelStyle: React.CSSProperties = {
|
||||
...props.labeltextStyle,
|
||||
...props.styles.label,
|
||||
marginBottom: props.labelSpacing
|
||||
};
|
||||
labelStyle.color = props.noodlNode.context.styles.resolveColor(labelStyle.color);
|
||||
|
||||
return (
|
||||
<div style={otherStyles}>
|
||||
<label htmlFor={props.id} style={labelStyle} noodl-style-tag="label">
|
||||
{props.label}
|
||||
</label>
|
||||
{inputWithWrapper}
|
||||
</div>
|
||||
);
|
||||
} else {
|
||||
return inputWithWrapper;
|
||||
}
|
||||
}
|
||||
|
||||
onKeyDown(e) {
|
||||
if (e.key === 'Enter' || e.which === 13) {
|
||||
this.props.onEnter && this.props.onEnter();
|
||||
}
|
||||
}
|
||||
|
||||
onChange(event) {
|
||||
const value = event.target.value;
|
||||
this.setText(value);
|
||||
}
|
||||
|
||||
focus() {
|
||||
this.ref.current && this.ref.current.focus();
|
||||
}
|
||||
|
||||
blur() {
|
||||
this.ref.current && this.ref.current.blur();
|
||||
}
|
||||
|
||||
hasFocus() {
|
||||
return document.activeElement === this.ref.current;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user