mirror of
https://github.com/Bubberstation/Bubberstation.git
synced 2026-08-24 21:48:39 +01:00
Typescript textarea component (#80276)
## About The Pull Request This refactor has been on my radar for a long time. There are oddities between this and the input component that are frustrating to work with. An issue was brought to my attention with the Interview panel which sends byond data on EVERY keystroke. This has been changed, documented, and made into typescript. It now sends onEnter, and the user is informed that they must press enter to submit.. It should be more obvious what the events for textarea _do_ actually so as to not make similar mistakes again. ## Why It's Good For The Game Less laggy input More typescript components/better dev exp ## Changelog 🆑 fix: Admin interview panel should feel snappier. /🆑
This commit is contained in:
@@ -1,240 +0,0 @@
|
||||
/**
|
||||
* @file
|
||||
* @copyright 2020 Aleksej Komarov
|
||||
* @author Warlockd
|
||||
* @license MIT
|
||||
*/
|
||||
|
||||
import { classes } from 'common/react';
|
||||
import { Component, createRef } from 'react';
|
||||
import { Box } from './Box';
|
||||
import { toInputValue } from './Input';
|
||||
import { KEY_ENTER, KEY_ESCAPE, KEY_TAB } from 'common/keycodes';
|
||||
|
||||
export class TextArea extends Component {
|
||||
constructor(props) {
|
||||
super(props);
|
||||
this.textareaRef = props.innerRef || createRef();
|
||||
this.state = {
|
||||
editing: false,
|
||||
scrolledAmount: 0,
|
||||
};
|
||||
const { dontUseTabForIndent = false } = props;
|
||||
this.handleOnInput = (e) => {
|
||||
const { editing } = this.state;
|
||||
const { onInput } = this.props;
|
||||
if (!editing) {
|
||||
this.setEditing(true);
|
||||
}
|
||||
if (onInput) {
|
||||
onInput(e, e.target.value);
|
||||
}
|
||||
};
|
||||
this.handleOnChange = (e) => {
|
||||
const { editing } = this.state;
|
||||
const { onChange } = this.props;
|
||||
if (editing) {
|
||||
this.setEditing(false);
|
||||
}
|
||||
if (onChange) {
|
||||
onChange(e, e.target.value);
|
||||
}
|
||||
};
|
||||
this.handleKeyPress = (e) => {
|
||||
const { editing } = this.state;
|
||||
const { onKeyPress } = this.props;
|
||||
if (!editing) {
|
||||
this.setEditing(true);
|
||||
}
|
||||
if (onKeyPress) {
|
||||
onKeyPress(e, e.target.value);
|
||||
}
|
||||
};
|
||||
this.handleKeyDown = (e) => {
|
||||
const { editing } = this.state;
|
||||
const { onChange, onInput, onEnter, onKey } = this.props;
|
||||
if (e.keyCode === KEY_ENTER) {
|
||||
this.setEditing(false);
|
||||
if (onChange) {
|
||||
onChange(e, e.target.value);
|
||||
}
|
||||
if (onInput) {
|
||||
onInput(e, e.target.value);
|
||||
}
|
||||
if (onEnter) {
|
||||
onEnter(e, e.target.value);
|
||||
}
|
||||
if (this.props.selfClear) {
|
||||
e.target.value = '';
|
||||
e.target.blur();
|
||||
}
|
||||
return;
|
||||
}
|
||||
if (e.keyCode === KEY_ESCAPE) {
|
||||
if (this.props.onEscape) {
|
||||
this.props.onEscape(e);
|
||||
}
|
||||
this.setEditing(false);
|
||||
if (this.props.selfClear) {
|
||||
e.target.value = '';
|
||||
} else {
|
||||
e.target.value = toInputValue(this.props.value);
|
||||
e.target.blur();
|
||||
}
|
||||
return;
|
||||
}
|
||||
if (!editing) {
|
||||
this.setEditing(true);
|
||||
}
|
||||
// Custom key handler
|
||||
if (onKey) {
|
||||
onKey(e, e.target.value);
|
||||
}
|
||||
if (!dontUseTabForIndent) {
|
||||
const keyCode = e.keyCode || e.which;
|
||||
if (keyCode === KEY_TAB) {
|
||||
e.preventDefault();
|
||||
const { value, selectionStart, selectionEnd } = e.target;
|
||||
e.target.value =
|
||||
value.substring(0, selectionStart) +
|
||||
'\t' +
|
||||
value.substring(selectionEnd);
|
||||
e.target.selectionEnd = selectionStart + 1;
|
||||
if (onInput) {
|
||||
onInput(e, e.target.value);
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
this.handleFocus = (e) => {
|
||||
const { editing } = this.state;
|
||||
if (!editing) {
|
||||
this.setEditing(true);
|
||||
}
|
||||
};
|
||||
this.handleBlur = (e) => {
|
||||
const { editing } = this.state;
|
||||
const { onChange } = this.props;
|
||||
if (editing) {
|
||||
this.setEditing(false);
|
||||
if (onChange) {
|
||||
onChange(e, e.target.value);
|
||||
}
|
||||
}
|
||||
};
|
||||
this.handleScroll = (e) => {
|
||||
const { displayedValue } = this.props;
|
||||
const input = this.textareaRef.current;
|
||||
if (displayedValue && input) {
|
||||
this.setState({
|
||||
scrolledAmount: input.scrollTop,
|
||||
});
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
componentDidMount() {
|
||||
const nextValue = this.props.value;
|
||||
const input = this.textareaRef.current;
|
||||
if (input) {
|
||||
input.value = toInputValue(nextValue);
|
||||
}
|
||||
if (this.props.autoFocus || this.props.autoSelect) {
|
||||
setTimeout(() => {
|
||||
input.focus();
|
||||
|
||||
if (this.props.autoSelect) {
|
||||
input.select();
|
||||
}
|
||||
}, 1);
|
||||
}
|
||||
}
|
||||
|
||||
componentDidUpdate(prevProps, prevState) {
|
||||
const prevValue = prevProps.value;
|
||||
const nextValue = this.props.value;
|
||||
const input = this.textareaRef.current;
|
||||
if (input && typeof nextValue === 'string' && prevValue !== nextValue) {
|
||||
input.value = toInputValue(nextValue);
|
||||
}
|
||||
}
|
||||
|
||||
setEditing(editing) {
|
||||
this.setState({ editing });
|
||||
}
|
||||
|
||||
getValue() {
|
||||
return this.textareaRef.current && this.textareaRef.current.value;
|
||||
}
|
||||
|
||||
render() {
|
||||
// Input only props
|
||||
const {
|
||||
onChange,
|
||||
onKeyDown,
|
||||
onKeyPress,
|
||||
onInput,
|
||||
onFocus,
|
||||
onBlur,
|
||||
onEnter,
|
||||
value,
|
||||
maxLength,
|
||||
placeholder,
|
||||
scrollbar,
|
||||
noborder,
|
||||
displayedValue,
|
||||
...boxProps
|
||||
} = this.props;
|
||||
|
||||
// Box props
|
||||
const { className, fluid, nowrap, ...rest } = boxProps;
|
||||
const { scrolledAmount } = this.state;
|
||||
return (
|
||||
<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={classes([
|
||||
'TextArea__textarea',
|
||||
scrollbar && 'TextArea__textarea--scrollable',
|
||||
nowrap && 'TextArea__nowrap',
|
||||
])}
|
||||
placeholder={placeholder}
|
||||
onChange={this.handleOnChange}
|
||||
onKeyDown={this.handleKeyDown}
|
||||
onKeyPress={this.handleKeyPress}
|
||||
onInput={this.handleOnInput}
|
||||
onFocus={this.handleFocus}
|
||||
onBlur={this.handleBlur}
|
||||
onScroll={this.handleScroll}
|
||||
maxLength={maxLength}
|
||||
style={{
|
||||
color: displayedValue ? 'rgba(0, 0, 0, 0)' : 'inherit',
|
||||
}}
|
||||
/>
|
||||
</Box>
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,181 @@
|
||||
/**
|
||||
* @file
|
||||
* @copyright 2020 Aleksej Komarov
|
||||
* @author Warlockd
|
||||
* @license MIT
|
||||
*/
|
||||
|
||||
import { classes } from 'common/react';
|
||||
import {
|
||||
forwardRef,
|
||||
useEffect,
|
||||
useState,
|
||||
RefObject,
|
||||
useRef,
|
||||
useImperativeHandle,
|
||||
} from 'react';
|
||||
import { toInputValue } from './Input';
|
||||
import { KEY } from 'common/keys';
|
||||
import { Box, BoxProps } from './Box';
|
||||
import { ChangeEvent, KeyboardEvent } from 'react';
|
||||
|
||||
type Props = Partial<{
|
||||
autoFocus: boolean;
|
||||
autoSelect: boolean;
|
||||
displayedValue: string;
|
||||
dontUseTabForIndent: boolean;
|
||||
maxLength: number;
|
||||
noborder: boolean;
|
||||
// This fires when: value changes
|
||||
onChange: (event: ChangeEvent<HTMLTextAreaElement>, value: string) => void;
|
||||
// This fires when: enter is pressed
|
||||
onEnter: (event: KeyboardEvent<HTMLTextAreaElement>, value: string) => void;
|
||||
// This fires when: escape is pressed
|
||||
onEscape: (event: KeyboardEvent<HTMLTextAreaElement>) => void;
|
||||
placeholder: string;
|
||||
scrollbar: boolean;
|
||||
selfClear: boolean;
|
||||
value: string;
|
||||
}> &
|
||||
BoxProps;
|
||||
|
||||
export const TextArea = forwardRef(
|
||||
(props: Props, forwardedRef: RefObject<HTMLTextAreaElement>) => {
|
||||
const {
|
||||
autoFocus,
|
||||
autoSelect,
|
||||
displayedValue,
|
||||
dontUseTabForIndent,
|
||||
maxLength,
|
||||
noborder,
|
||||
onChange,
|
||||
onEnter,
|
||||
onEscape,
|
||||
placeholder,
|
||||
scrollbar,
|
||||
selfClear,
|
||||
value,
|
||||
...boxProps
|
||||
} = props;
|
||||
const { className, fluid, nowrap, ...rest } = boxProps;
|
||||
|
||||
const textareaRef = useRef<HTMLTextAreaElement>(null);
|
||||
const [scrolledAmount, setScrolledAmount] = useState(0);
|
||||
|
||||
const handleKeyDown = (event: KeyboardEvent<HTMLTextAreaElement>) => {
|
||||
if (event.key === KEY.Enter) {
|
||||
if (event.shiftKey) {
|
||||
onChange?.(event as any, event.currentTarget.value);
|
||||
return;
|
||||
}
|
||||
|
||||
onEnter?.(event, event.currentTarget.value);
|
||||
if (selfClear) {
|
||||
event.currentTarget.value = '';
|
||||
}
|
||||
event.currentTarget.blur();
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
if (event.key === KEY.Escape) {
|
||||
onEscape?.(event);
|
||||
if (selfClear) {
|
||||
event.currentTarget.value = '';
|
||||
} else {
|
||||
event.currentTarget.value = toInputValue(value);
|
||||
event.currentTarget.blur();
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
if (!dontUseTabForIndent && event.key === KEY.Tab) {
|
||||
event.preventDefault();
|
||||
const { value, selectionStart, selectionEnd } = event.currentTarget;
|
||||
event.currentTarget.value =
|
||||
value.substring(0, selectionStart) +
|
||||
'\t' +
|
||||
value.substring(selectionEnd);
|
||||
event.currentTarget.selectionEnd = selectionStart + 1;
|
||||
}
|
||||
};
|
||||
|
||||
useImperativeHandle(
|
||||
forwardedRef,
|
||||
() => textareaRef.current as HTMLTextAreaElement,
|
||||
);
|
||||
|
||||
useEffect(() => {
|
||||
const input = textareaRef.current;
|
||||
if (!input) return;
|
||||
|
||||
input.value = toInputValue(value);
|
||||
|
||||
if (autoFocus || autoSelect) {
|
||||
setTimeout(() => {
|
||||
input.focus();
|
||||
|
||||
if (autoSelect) {
|
||||
input.select();
|
||||
}
|
||||
}, 1);
|
||||
}
|
||||
}, []);
|
||||
|
||||
return (
|
||||
<Box
|
||||
className={classes([
|
||||
'TextArea',
|
||||
fluid && 'TextArea--fluid',
|
||||
noborder && 'TextArea--noborder',
|
||||
className,
|
||||
])}
|
||||
{...rest}
|
||||
>
|
||||
{!!displayedValue && (
|
||||
<div
|
||||
style={{
|
||||
height: '100%',
|
||||
overflow: 'hidden',
|
||||
position: 'absolute',
|
||||
width: '100%',
|
||||
}}
|
||||
>
|
||||
<div
|
||||
className={classes([
|
||||
'TextArea__textarea',
|
||||
'TextArea__textarea_custom',
|
||||
])}
|
||||
style={{
|
||||
transform: `translateY(-${scrolledAmount}px)`,
|
||||
}}
|
||||
>
|
||||
{displayedValue}
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
<textarea
|
||||
className={classes([
|
||||
'TextArea__textarea',
|
||||
scrollbar && 'TextArea__textarea--scrollable',
|
||||
nowrap && 'TextArea__nowrap',
|
||||
])}
|
||||
maxLength={maxLength}
|
||||
onChange={(event) => onChange?.(event, event.target.value)}
|
||||
onKeyDown={handleKeyDown}
|
||||
onScroll={() => {
|
||||
if (displayedValue && textareaRef.current) {
|
||||
setScrolledAmount(textareaRef.current.scrollTop);
|
||||
}
|
||||
}}
|
||||
placeholder={placeholder}
|
||||
ref={textareaRef}
|
||||
style={{
|
||||
color: displayedValue ? 'rgba(0, 0, 0, 0)' : 'inherit',
|
||||
}}
|
||||
/>
|
||||
</Box>
|
||||
);
|
||||
},
|
||||
);
|
||||
@@ -140,7 +140,7 @@ export const FaxMainPanel = (props) => {
|
||||
placeholder="Your message here..."
|
||||
height="200px"
|
||||
value={rawText}
|
||||
onInput={(e, value) => {
|
||||
onChange={(e, value) => {
|
||||
setRawText(value);
|
||||
}}
|
||||
/>
|
||||
|
||||
@@ -125,7 +125,7 @@ const MessageInput = (props) => {
|
||||
placeholder="Type the message you want to send..."
|
||||
height="200px"
|
||||
mb={1}
|
||||
onInput={(e, value) => {
|
||||
onChange={(e, value) => {
|
||||
setMessageText(value);
|
||||
}}
|
||||
/>
|
||||
|
||||
@@ -164,7 +164,7 @@ const ReportText = (props) => {
|
||||
<TextArea
|
||||
height="200px"
|
||||
mb={1}
|
||||
onInput={(_, value) => setCommandReport(value)}
|
||||
onChange={(_, value) => setCommandReport(value)}
|
||||
value={commandReport}
|
||||
/>
|
||||
<Stack vertical>
|
||||
|
||||
@@ -84,7 +84,7 @@ const MessageModal = (props) => {
|
||||
width="80vw"
|
||||
backgroundColor="black"
|
||||
textColor="white"
|
||||
onInput={(_, value) => {
|
||||
onChange={(_, value) => {
|
||||
setInput(value.substring(0, maxMessageLength));
|
||||
}}
|
||||
value={input}
|
||||
|
||||
+74
-50
@@ -7,54 +7,62 @@ import {
|
||||
} from '../components';
|
||||
import { Window } from '../layouts';
|
||||
import { useBackend } from '../backend';
|
||||
import { ReactNode } from 'react';
|
||||
|
||||
type Data = {
|
||||
connected: boolean;
|
||||
is_admin: boolean;
|
||||
questions: Question[];
|
||||
queue_pos: number;
|
||||
read_only: boolean;
|
||||
status: string;
|
||||
welcome_message: string;
|
||||
};
|
||||
|
||||
type Question = {
|
||||
qidx: number;
|
||||
question: string;
|
||||
response: string;
|
||||
};
|
||||
|
||||
enum STATUS {
|
||||
Approved = 'interview_approved',
|
||||
Denied = 'interview_denied',
|
||||
}
|
||||
|
||||
// Matches a complete markdown-style link, capturing the whole [...](...)
|
||||
const linkRegex = /(\[[^[]+\]\([^)]+\))/;
|
||||
// Decomposes a markdown-style link into the link and display text
|
||||
const linkDecomposeRegex = /\[([^[]+)\]\(([^)]+)\)/;
|
||||
|
||||
// Renders any markdown-style links within a provided body of text
|
||||
const linkifyText = (text: string) => {
|
||||
let parts: ReactNode[] = text.split(linkRegex);
|
||||
for (let i = 1; i < parts.length; i += 2) {
|
||||
const match = linkDecomposeRegex.exec(parts[i] as string);
|
||||
if (!match) continue;
|
||||
|
||||
parts[i] = (
|
||||
<a key={'link' + i} href={match[2]}>
|
||||
{match[1]}
|
||||
</a>
|
||||
);
|
||||
}
|
||||
return parts;
|
||||
};
|
||||
|
||||
export const Interview = (props) => {
|
||||
const { act, data } = useBackend();
|
||||
const { act, data } = useBackend<Data>();
|
||||
const {
|
||||
welcome_message,
|
||||
questions,
|
||||
read_only,
|
||||
queue_pos,
|
||||
is_admin,
|
||||
status,
|
||||
connected,
|
||||
is_admin,
|
||||
questions = [], // TODO: Remove default
|
||||
queue_pos,
|
||||
read_only,
|
||||
status,
|
||||
welcome_message = '',
|
||||
} = data;
|
||||
|
||||
const rendered_status = (status) => {
|
||||
switch (status) {
|
||||
case 'interview_approved':
|
||||
return <NoticeBox success>This interview was approved.</NoticeBox>;
|
||||
case 'interview_denied':
|
||||
return <NoticeBox danger>This interview was denied.</NoticeBox>;
|
||||
default:
|
||||
return (
|
||||
<NoticeBox info>
|
||||
Your answers have been submitted. You are position {queue_pos} in
|
||||
queue.
|
||||
</NoticeBox>
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
// Matches a complete markdown-style link, capturing the whole [...](...)
|
||||
const link_regex = /(\[[^[]+\]\([^)]+\))/;
|
||||
// Decomposes a markdown-style link into the link and display text
|
||||
const link_decompose_regex = /\[([^[]+)\]\(([^)]+)\)/;
|
||||
|
||||
// Renders any markdown-style links within a provided body of text
|
||||
const linkify_text = (text) => {
|
||||
let parts = text.split(link_regex);
|
||||
for (let i = 1; i < parts.length; i += 2) {
|
||||
const match = link_decompose_regex.exec(parts[i]);
|
||||
parts[i] = (
|
||||
<a key={'link' + i} href={match[2]}>
|
||||
{match[1]}
|
||||
</a>
|
||||
);
|
||||
}
|
||||
return parts;
|
||||
};
|
||||
|
||||
return (
|
||||
<Window
|
||||
width={500}
|
||||
@@ -64,10 +72,9 @@ export const Interview = (props) => {
|
||||
<Window.Content scrollable>
|
||||
{(!read_only && (
|
||||
<Section title="Welcome!">
|
||||
<p>{linkify_text(welcome_message)}</p>
|
||||
<p>{linkifyText(welcome_message)}</p>
|
||||
</Section>
|
||||
)) ||
|
||||
rendered_status(status)}
|
||||
)) || <RenderedStatus status={status} queue_pos={queue_pos} />}
|
||||
<Section
|
||||
title="Questionnaire"
|
||||
buttons={
|
||||
@@ -110,7 +117,7 @@ export const Interview = (props) => {
|
||||
)}
|
||||
{questions.map(({ qidx, question, response }) => (
|
||||
<Section key={qidx} title={`Question ${qidx}`}>
|
||||
<p>{linkify_text(question)}</p>
|
||||
<p>{linkifyText(question)}</p>
|
||||
{((read_only || is_admin) && (
|
||||
<BlockQuote>{response || 'No response.'}</BlockQuote>
|
||||
)) || (
|
||||
@@ -119,11 +126,10 @@ export const Interview = (props) => {
|
||||
fluid
|
||||
height={10}
|
||||
maxLength={500}
|
||||
placeholder="Write your response here, max of 500 characters."
|
||||
onChange={(e, input) =>
|
||||
input !== response &&
|
||||
placeholder="Write your response here, max of 500 characters. Press enter to submit."
|
||||
onEnter={(e, input) =>
|
||||
act('update_answer', {
|
||||
qidx: qidx,
|
||||
qidx,
|
||||
answer: input,
|
||||
})
|
||||
}
|
||||
@@ -136,3 +142,21 @@ export const Interview = (props) => {
|
||||
</Window>
|
||||
);
|
||||
};
|
||||
|
||||
const RenderedStatus = (props: { status: string; queue_pos: number }) => {
|
||||
const { status, queue_pos } = props;
|
||||
|
||||
switch (status) {
|
||||
case STATUS.Approved:
|
||||
return <NoticeBox success>This interview was approved.</NoticeBox>;
|
||||
case STATUS.Denied:
|
||||
return <NoticeBox danger>This interview was denied.</NoticeBox>;
|
||||
default:
|
||||
return (
|
||||
<NoticeBox info>
|
||||
Your answers have been submitted. You are position {queue_pos} in
|
||||
queue.
|
||||
</NoticeBox>
|
||||
);
|
||||
}
|
||||
};
|
||||
@@ -30,7 +30,7 @@ export const LingMMITalk = (props) => {
|
||||
<TextArea
|
||||
height="60px"
|
||||
placeholder="Send a message to have our decoy brain speak."
|
||||
onInput={(_, value) => setmmiMessage(value)}
|
||||
onChange={(_, value) => setmmiMessage(value)}
|
||||
value={mmiMessage}
|
||||
/>
|
||||
</Stack.Item>
|
||||
|
||||
@@ -200,7 +200,7 @@ export class LuaEditor extends Component {
|
||||
height="100%"
|
||||
value={scriptInput}
|
||||
fontFamily="Consolas"
|
||||
onInput={(_, value) =>
|
||||
onChange={(_, value) =>
|
||||
this.setState({ scriptInput: value })
|
||||
}
|
||||
displayedValue={
|
||||
|
||||
@@ -201,7 +201,7 @@ const MafiaChat = (props) => {
|
||||
maxLength={300}
|
||||
className="Section__title candystripe"
|
||||
onChange={(e, value) => setMessagingBox(value)}
|
||||
placeholder={'Type to chat'}
|
||||
placeholder="Type to chat"
|
||||
value={message_to_send}
|
||||
/>
|
||||
<Stack grow>
|
||||
@@ -382,7 +382,7 @@ const MafiaNotesTab = (props) => {
|
||||
maxLength={600}
|
||||
className="Section__title candystripe"
|
||||
onChange={(_, value) => setNotesMessage(value)}
|
||||
placeholder={'Insert Notes...'}
|
||||
placeholder="Insert Notes..."
|
||||
value={note_message}
|
||||
/>
|
||||
<Stack grow>
|
||||
|
||||
@@ -329,7 +329,7 @@ const SendToAllSection = (props) => {
|
||||
height={6}
|
||||
value={message}
|
||||
placeholder="Send message to everyone..."
|
||||
onInput={(_: any, v: string) => setmessage(v)}
|
||||
onChange={(event, value: string) => setmessage(value)}
|
||||
/>
|
||||
</Section>
|
||||
</>
|
||||
|
||||
@@ -307,9 +307,9 @@ class NotePadTextArea extends Component<NotePadTextAreaProps> {
|
||||
|
||||
return (
|
||||
<TextArea
|
||||
innerRef={this.innerRef}
|
||||
onInput={(_, value) => setText(value)}
|
||||
className={'NtosNotepad__textarea'}
|
||||
ref={this.innerRef}
|
||||
onChange={(_, value) => setText(value)}
|
||||
className="NtosNotepad__textarea"
|
||||
scroll
|
||||
nowrap={!wordWrap}
|
||||
value={text}
|
||||
|
||||
@@ -387,9 +387,9 @@ export class PrimaryView extends Component {
|
||||
textColor={useColor}
|
||||
fontFamily={useFont}
|
||||
bold={useBold}
|
||||
height={'100%'}
|
||||
height="100%"
|
||||
backgroundColor={paper_color}
|
||||
onInput={(e, text) => {
|
||||
onChange={(e, text) => {
|
||||
setTextAreaText(text);
|
||||
|
||||
if (this.scrollableRef.current) {
|
||||
|
||||
@@ -117,7 +117,7 @@ const InputArea = (props: {
|
||||
event.preventDefault();
|
||||
act('submit', { entry: input });
|
||||
}}
|
||||
onInput={(_, value) => onType(value)}
|
||||
onChange={(_, value) => onType(value)}
|
||||
placeholder="Type something..."
|
||||
value={input}
|
||||
/>
|
||||
|
||||
Reference in New Issue
Block a user