TextBox Shift-Enter (Multilines)

This commit is contained in:
ItsSelis
2023-05-19 22:49:46 +02:00
parent f7eb1e0ec8
commit 7ddff379c7
5 changed files with 99 additions and 27 deletions

View File

@@ -122,8 +122,8 @@
data["multiline"] = multiline
data["placeholder"] = default // Default is a reserved keyword
data["swapped_buttons"] = !user.client.prefs.tgui_swapped_buttons
// CHOMPedit - prevent_enter should be completely removed in the future
data["title"] = title
data["prevent_enter"] = prevent_enter
return data
/datum/tgui_input_text/tgui_data(mob/user)

View File

@@ -15,9 +15,10 @@ export class TextArea extends Component {
constructor(props, context) {
super(props, context);
this.textareaRef = props.innerRef || createRef();
this.fillerRef = createRef();
// CHOMPedit
this.state = {
editing: false,
scrolledAmount: 0,
};
const { dontUseTabForIndent = false } = props;
this.handleOnInput = (e) => {
@@ -52,7 +53,8 @@ export class TextArea extends Component {
};
this.handleKeyDown = (e) => {
const { editing } = this.state;
const { onChange, onInput, onEnter, onKeyDown } = this.props;
// CHOMPedit
const { onChange, onInput, onEnter, onKey } = this.props;
if (e.keyCode === KEY_ENTER) {
this.setEditing(false);
if (onChange) {
@@ -86,8 +88,10 @@ export class TextArea extends Component {
if (!editing) {
this.setEditing(true);
}
if (onKeyDown) {
onKeyDown(e, e.target.value);
// CHOMPedit
// Custom key handler
if (onKey) {
onKey(e, e.target.value);
}
if (!dontUseTabForIndent) {
const keyCode = e.keyCode || e.which;
@@ -96,6 +100,10 @@ export class TextArea extends Component {
const { value, selectionStart, selectionEnd } = e.target;
e.target.value = value.substring(0, selectionStart) + '\t' + value.substring(selectionEnd);
e.target.selectionEnd = selectionStart + 1;
// CHOMPedit
if (onInput) {
onInput(e, e.target.value);
}
}
}
};
@@ -115,6 +123,16 @@ export class TextArea extends Component {
}
}
};
// CHOMPedit Start
this.handleScroll = (e) => {
const { displayedValue } = this.props;
const input = this.textareaRef.current;
if (displayedValue && input) {
this.setState({
scrolledAmount: input.scrollTop,
});
}
};
}
componentDidMount() {
@@ -123,8 +141,15 @@ export class TextArea extends Component {
if (input) {
input.value = toInputValue(nextValue);
}
if (this.props.autoFocus) {
setTimeout(() => input.focus(), 1);
if (this.props.autoFocus || this.props.autoSelect) {
setTimeout(() => {
input.focus();
if (this.props.autoSelect) {
input.select();
}
}, 1);
// CHOMPedit End
}
}
@@ -158,15 +183,37 @@ export class TextArea extends Component {
value,
maxLength,
placeholder,
// CHOMPedit Start
scrollbar,
noborder,
displayedValue,
...boxProps
} = this.props;
// Box props
const { className, fluid, ...rest } = boxProps;
const { className, fluid, nowrap, ...rest } = boxProps;
const { scrolledAmount } = this.state;
return (
<Box className={classes(['TextArea', fluid && 'TextArea--fluid', className])} {...rest}>
<Box
className={classes(['TextArea', fluid && 'TextArea--fluid', noborder && 'TextArea--noborder', className])}
{...rest}>
{!!displayedValue && (
<Box position="absolute" width="100%" height="100%" overflow="hidden">
<div
className={classes(['TextArea__textarea', 'TextArea__textarea_custom'])}
style={{
'transform': `translateY(-${scrolledAmount}px)`,
}}>
{displayedValue}
</div>
</Box>
)}
<textarea
ref={this.textareaRef}
className="TextArea__textarea"
className={classes([
'TextArea__textarea',
scrollbar && 'TextArea__textarea--scrollable',
nowrap && 'TextArea__nowrap',
])}
placeholder={placeholder}
onChange={this.handleOnChange}
onKeyDown={this.handleKeyDown}
@@ -174,8 +221,13 @@ export class TextArea extends Component {
onInput={this.handleOnInput}
onFocus={this.handleFocus}
onBlur={this.handleBlur}
onScroll={this.handleScroll}
maxLength={maxLength}
style={{
'color': displayedValue ? 'rgba(0, 0, 0, 0)' : 'inherit',
}}
/>
{/* CHOMPedit End */}
</Box>
);
}

View File

@@ -1,6 +1,7 @@
import { Loader } from './common/Loader';
import { InputButtons } from './common/InputButtons';
import { useBackend, useLocalState } from '../backend';
import { KEY_ENTER, KEY_ESCAPE } from '../../common/keycodes'; // CHOMPedit
import { Box, Section, Stack, TextArea } from '../components';
import { Window } from '../layouts';
@@ -12,37 +13,51 @@ type TextInputData = {
placeholder: string;
timeout: number;
title: string;
prevent_enter: boolean;
// CHOMPedit Start
};
export const sanitizeMultiline = (toSanitize: string) => {
return toSanitize.replace(/(\n|\r\n){3,}/, '\n\n');
};
export const removeAllSkiplines = (toSanitize: string) => {
return toSanitize.replace(/[\r\n]+/, '');
};
export const TextInputModal = (props, context) => {
const { act, data } = useBackend<TextInputData>(context);
const { large_buttons, max_length, message = '', multiline, placeholder, timeout, title, prevent_enter } = data;
const { large_buttons, max_length, message = '', multiline, placeholder, timeout, title } = data;
const [input, setInput] = useLocalState<string>(context, 'input', placeholder || '');
const onType = (value: string) => {
if (value === input) {
return;
}
setInput(value);
const sanitizedInput = multiline ? sanitizeMultiline(value) : removeAllSkiplines(value);
setInput(sanitizedInput);
};
const visualMultiline = multiline || input.length >= 30;
// Dynamically changes the window height based on the message.
const windowHeight =
135 +
(message.length > 30 ? Math.ceil(message.length / 4) : 0) +
(multiline || input.length >= 30 ? 75 : 0) +
(visualMultiline ? 75 : 0) +
(message.length && large_buttons ? 5 : 0);
return (
<Window title={title} width={325} height={windowHeight}>
{timeout && <Loader value={timeout} />}
<Window.Content
onEscape={() => act('cancel')}
onEnter={(event) => {
if (!prevent_enter) {
onKeyDown={(event) => {
const keyCode = window.event ? event.which : event.keyCode;
if (keyCode === KEY_ENTER && (!visualMultiline || !event.shiftKey)) {
act('submit', { entry: input });
event.preventDefault();
}
if (keyCode === KEY_ESCAPE) {
act('cancel');
}
}}>
{/* CHOMPedit End */}
<Section fill>
<Stack fill vertical>
<Stack.Item>
@@ -64,9 +79,11 @@ export const TextInputModal = (props, context) => {
/** Gets the user input and invalidates if there's a constraint. */
const InputArea = (props, context) => {
const { act, data } = useBackend<TextInputData>(context);
const { max_length, multiline, prevent_enter } = data;
const { max_length, multiline } = data; // CHOMPedit
const { input, onType } = props;
const visualMultiline = multiline || input.length >= 30; // CHOMPedit
return (
<TextArea
autoFocus
@@ -75,10 +92,13 @@ const InputArea = (props, context) => {
maxLength={max_length}
onEscape={() => act('cancel')}
onEnter={(event) => {
if (!prevent_enter) {
act('submit', { entry: input });
event.preventDefault();
// CHOMPedit Start
if (visualMultiline && event.shiftKey) {
return;
}
event.preventDefault();
act('submit', { entry: input });
// CHOMPedit End
}}
onInput={(_, value) => onType(value)}
placeholder="Type something..."

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long