Remove/debounced-input (#5308)

* first pass

* remove debounced input

* fix onPaste type errors
This commit is contained in:
Jack Kavanagh authored and GitHub committed 2022-10-21 15:01:50 +00:00
1 parent 4f9027ad94
commit 2f76cb0c6d
9 files changed
+49 -236

No files matched your search

@@ -1,171 +0,0 @@
import { autoBindMethodsForReact } from 'class-autobind-decorator';
import React, { PureComponent } from 'react';
import { AUTOBIND_CFG } from '../../../common/constants';
import { debounce } from '../../../common/misc';
interface Props {
onChange: (value: string) => void;
onFocus?: (event: React.FocusEvent<HTMLTextAreaElement | HTMLInputElement>) => void;
onBlur?: (event: React.FocusEvent<HTMLTextAreaElement | HTMLInputElement>) => void;
textarea?: boolean;
delay?: number;
placeholder?: string;
initialValue?: string;
}
@autoBindMethodsForReact(AUTOBIND_CFG)
export class DebouncedInput extends PureComponent<Props> {
_hasFocus = false;
_input: HTMLTextAreaElement | HTMLInputElement | null = null;
_handleValueChange: Props['onChange'] | null = null;
constructor(props: Props) {
super(props);
if (!props.delay) {
this._handleValueChange = props.onChange;
} else {
this._handleValueChange = debounce(props.onChange, props.delay || 500);
}
}
componentDidMount() {
if (this._input && this.props.initialValue) {
this._input.value = this.props.initialValue;
}
}
_handleChange(event: React.ChangeEvent<HTMLTextAreaElement | HTMLInputElement>) {
this._handleValueChange?.(event.target.value);
}
_handleFocus(event: React.FocusEvent<HTMLTextAreaElement | HTMLInputElement>) {
this._hasFocus = true;
this.props.onFocus?.(event);
}
_handleBlur(event: React.FocusEvent<HTMLTextAreaElement | HTMLInputElement>) {
this._hasFocus = false;
this.props.onBlur?.(event);
}
_setRef(input: HTMLTextAreaElement | HTMLInputElement) {
this._input = input;
}
setAttribute(name: string, value: string) {
this._input?.setAttribute(name, value);
}
removeAttribute(name: string) {
this._input?.removeAttribute(name);
}
getAttribute(name: string) {
this._input?.getAttribute(name);
}
hasFocus() {
return this._hasFocus;
}
getSelectionStart() {
if (this._input) {
return this._input.selectionStart;
} else {
return -1;
}
}
getSelectionEnd() {
if (this._input) {
return this._input.selectionEnd;
} else {
return -1;
}
}
focus() {
if (this._input) {
this._input.focus();
}
}
focusEnd() {
if (this._input) {
// Hack to focus the end (set value to current value);
this._input.value = this.getValue();
this._input.focus();
}
}
blur() {
if (this._input) {
this._input.blur();
}
}
select() {
if (this._input) {
this._input.select();
}
}
getValue() {
if (this._input) {
return this._input.value;
} else {
return '';
}
}
setValue(value: string) {
if (this._input) {
this._input.value = value;
}
}
render() {
const {
onChange,
// eslint-disable-line @typescript-eslint/no-unused-vars
onFocus,
// eslint-disable-line @typescript-eslint/no-unused-vars
onBlur,
// eslint-disable-line @typescript-eslint/no-unused-vars
delay,
// eslint-disable-line @typescript-eslint/no-unused-vars
textarea,
initialValue,
...props
} = this.props;
if (textarea) {
return (
<textarea
ref={ref => {
this._input = ref;
}}
{...props}
onChange={this._handleChange}
onFocus={this._handleFocus}
onBlur={this._handleBlur}
/>
);
} else {
return (
<input
ref={ref => {
this._input = ref;
}}
{...props}
onChange={this._handleChange}
onFocus={this._handleFocus}
onBlur={this._handleBlur}
/>
);
}
}
}
@@ -599,7 +599,7 @@ export class UnconnectedCodeEditor extends Component<CodeEditorProps, State> {
this.codeMirror.on('endCompletion', this._codemirrorEndCompletion);
this.codeMirror.on('focus', this._codemirrorFocus);
this.codeMirror.on('blur', this._codemirrorBlur);
this.codeMirror.on('paste', this._codemirrorPaste);
this.codeMirror.on('paste', (_, event) => this.props.onPaste?.(event));
this.codeMirror.on('scroll', this._codemirrorScroll);
this.codeMirror.on('fold', this._codemirrorToggleFold);
this.codeMirror.on('unfold', this._codemirrorToggleFold);
@@ -1112,10 +1112,6 @@ export class UnconnectedCodeEditor extends Component<CodeEditorProps, State> {
}
}
_codemirrorPaste(_cm: CodeMirror.Editor, event: ClipboardEvent) {
this.props.onPaste?.(event);
}
_codemirrorPreventWhenTypePassword(_cm: CodeMirror.Editor, event: Event) {
const { type } = this.props;
@@ -4,8 +4,7 @@ import React, { Fragment, PureComponent } from 'react';
import ReactDOM from 'react-dom';
import { AUTOBIND_CFG } from '../../../common/constants';
import { DebouncedInput } from '../base/debounced-input';
import { CodeEditor, CodeEditorOnChange, UnconnectedCodeEditor } from './code-editor';
import { CodeEditor, CodeEditorOnChange, UnconnectedCodeEditor } from './code-editor';
const MODE_INPUT = 'input';
const MODE_EDITOR = 'editor';
const TYPE_TEXT = 'text';
@@ -38,7 +37,7 @@ interface State {
@autoBindMethodsForReact(AUTOBIND_CFG)
export class OneLineEditor extends PureComponent<Props, State> {
_editor: UnconnectedCodeEditor | null = null;
_input: DebouncedInput | null = null;
_input: HTMLInputElement | null = null;
_mouseEnterTimeout: NodeJS.Timeout | null = null;
constructor(props: Props) {
@@ -62,12 +61,17 @@ export class OneLineEditor extends PureComponent<Props, State> {
focus(setToEnd = false) {
if (this.state.mode === MODE_EDITOR) {
if (this._editor && !this._editor?.hasFocus()) {
if (this._editor && !this._editor.hasFocus()) {
setToEnd ? this._editor?.focusEnd() : this._editor?.focus();
}
} else {
if (this._input && !this._input?.hasFocus()) {
setToEnd ? this._input?.focusEnd() : this._input?.focus();
if (this._input && this._input !== document.activeElement) {
if (this._input) {
if (setToEnd) {
this._input.value = this._input.value;
}
this._input.focus();
}
}
}
}
@@ -88,7 +92,7 @@ export class OneLineEditor extends PureComponent<Props, State> {
if (this.state.mode === MODE_EDITOR) {
return this._editor?.getValue();
} else {
return this._input?.getValue();
return this._input?.value;
}
}
@@ -97,7 +101,6 @@ export class OneLineEditor extends PureComponent<Props, State> {
return this._editor?.getSelectionStart();
} else {
console.warn('Tried to get selection start of one-line-editor when <input>');
// @ts-expect-error -- TSCONVERSION
return this._input?.value.length;
}
}
@@ -107,7 +110,6 @@ export class OneLineEditor extends PureComponent<Props, State> {
return this._editor?.getSelectionEnd();
} else {
console.warn('Tried to get selection end of one-line-editor when <input>');
// @ts-expect-error -- TSCONVERSION
return this._input?.value.length;
}
}
@@ -183,14 +185,18 @@ export class OneLineEditor extends PureComponent<Props, State> {
_handleInputFocus(event: React.FocusEvent<HTMLTextAreaElement | HTMLInputElement, Element>) {
// If we're focusing the whole thing, blur the input. This happens when
// the user tabs to the field.
const start = this._input?.getSelectionStart();
if (!this._input) {
return;
}
const start = this._input.selectionStart;
const end = this._input?.getSelectionEnd();
const end = this._input.selectionEnd;
const focusedFromTabEvent = start === 0 && end === event.target.value.length;
if (focusedFromTabEvent) {
this._input?.focusEnd();
this._input.value = this._input.value;
this._input.focus();
// Also convert to editor if we tabbed to it. Just in case the user
// needs an editor
@@ -204,10 +210,9 @@ export class OneLineEditor extends PureComponent<Props, State> {
this.props.onFocus?.(event);
}
_handleInputChange(value: string) {
_handleInputChange(event: React.ChangeEvent<HTMLInputElement>) {
this._convertToEditorPreserveFocus();
this.props.onChange?.(value);
this.props.onChange?.(event.target.value);
}
_handleInputKeyDown(event: React.KeyboardEvent<HTMLInputElement>) {
@@ -219,7 +224,6 @@ export class OneLineEditor extends PureComponent<Props, State> {
_handleInputBlur(event: React.FocusEvent<HTMLTextAreaElement | HTMLInputElement>) {
// Set focused state
this._input?.removeAttribute('data-focused');
this.props.onBlur?.(event);
}
@@ -231,7 +235,6 @@ export class OneLineEditor extends PureComponent<Props, State> {
// Set focused state
this._editor?.removeAttribute('data-focused');
if (!this.props.forceEditor) {
// Convert back to input sometime in the future.
// NOTE: this was originally added because the input would disappear if
@@ -241,7 +244,6 @@ export class OneLineEditor extends PureComponent<Props, State> {
this._convertToInputIfNotFocused();
}, 2000);
}
this.props.onBlur?.(event);
}
@@ -277,9 +279,9 @@ export class OneLineEditor extends PureComponent<Props, State> {
return;
}
if (this._input?.hasFocus()) {
const start = this._input?.getSelectionStart();
const end = this._input?.getSelectionEnd();
if (this._input === document.activeElement) {
const start = this._input?.selectionStart;
const end = this._input?.selectionEnd;
if (start === null || end === null) {
return;
}
@@ -326,7 +328,7 @@ export class OneLineEditor extends PureComponent<Props, State> {
this._editor = editor;
}
_setInputRef(input: DebouncedInput) {
_setInputRef(input: HTMLInputElement) {
this._input = input;
}
@@ -391,9 +393,8 @@ export class OneLineEditor extends PureComponent<Props, State> {
);
} else {
return (
<DebouncedInput
<input
ref={this._setInputRef}
// @ts-expect-error -- TSCONVERSION
id={id}
type={type}
className={className}
@@ -409,7 +410,7 @@ export class OneLineEditor extends PureComponent<Props, State> {
onMouseEnter={this._handleInputMouseEnter}
onMouseLeave={this._handleInputMouseLeave}
onDragEnter={this._handleInputDragEnter}
onPaste={onPaste}
onPaste={e => onPaste?.(e.nativeEvent)}
onFocus={this._handleInputFocus}
onKeyDown={this._handleInputKeyDown}
/>
@@ -2,7 +2,6 @@ import { SchemaReference } from 'codemirror-graphql/utils/SchemaReference';
import { GraphQLEnumType, GraphQLField, GraphQLNamedType, GraphQLSchema, GraphQLType, isNamedType } from 'graphql';
import React, { FC, useCallback, useEffect, useRef, useState } from 'react';
import { DebouncedInput } from '../base/debounced-input';
import { useDocBodyKeyboardShortcuts } from '../keydown-binder';
import { GraphQLExplorerEnum } from './graph-ql-explorer-enum';
import { GraphQLExplorerField } from './graph-ql-explorer-field';
@@ -68,10 +67,9 @@ interface State extends HistoryItem {
filter: string;
}
const SEARCH_UPDATE_DELAY_IN_MS = 200;
export const GraphQLExplorer: FC<Props> = ({ schema, handleClose, visible, reference }) => {
const [{ currentType, currentField, history, filter }, setState] = useState<State>({ history:[], filter: '' });
const inputRef = useRef<DebouncedInput>(null);
const inputRef = useRef<HTMLInputElement>(null);
const addToHistory = useCallback(({ currentType, currentField, history }: State) => {
if (!currentType && !currentField) {
@@ -172,19 +170,20 @@ export const GraphQLExplorer: FC<Props> = ({ schema, handleClose, visible, refer
<>
<div className="graphql-explorer__search">
<div className="form-control form-control--outlined form-control--btn-right">
<DebouncedInput
<input
ref={inputRef}
onChange={filter => setState(state => ({ ...state, filter }))}
onChange={event => setState(state => ({ ...state, filter: event.target.value }))}
placeholder="Search the docs..."
delay={SEARCH_UPDATE_DELAY_IN_MS}
initialValue={filter}
defaultValue={filter}
/>
{filter && (
<button
className="form-control__right"
onClick={() => {
inputRef.current?.setValue('');
setState(state => ({ ...state, filter:'' }));
if (inputRef.current) {
inputRef.current.value = '';
setState(state => ({ ...state, filter:'' }));
}
}}
>
<i className="fa fa-times-circle" />
@@ -137,10 +137,10 @@ class KeyValueEditorRowInternal extends PureComponent<Props, State> {
// Unfortunately, this is the easiest way to do this.
const currentValue = this._valueInput?.getValue();
// @ts-expect-error -- TSCONVERSION
const prefix = currentValue.slice(0, this._valueInput?.getSelectionStart());
// @ts-expect-error -- TSCONVERSION
const suffix = currentValue.slice(this._valueInput?.getSelectionEnd());
const start = this._valueInput?.getSelectionStart() || 0;
const end = this._valueInput?.getSelectionEnd() || 0;
const prefix = currentValue?.slice(0, start);
const suffix = currentValue?.slice(end);
const finalValue = `${prefix}${value}${suffix}`;
// Update type and value
@@ -6,7 +6,6 @@ import * as models from '../../../models/index';
import { isRemoteProject, projectHasSettings } from '../../../models/project';
import { removeProject } from '../../redux/modules/project';
import { selectActiveProject } from '../../redux/selectors';
import { DebouncedInput } from '../base/debounced-input';
import { type ModalHandle, Modal, ModalProps } from '../base/modal';
import { ModalBody } from '../base/modal-body';
import { ModalHeader } from '../base/modal-header';
@@ -51,13 +50,11 @@ export const ProjectSettingsModal = forwardRef<ProjectSettingsModalHandle, Modal
</>
)}
{!isRemote && (
<DebouncedInput
// @ts-expect-error -- TSCONVERSION props are spread into an input element
<input
type="text"
delay={500}
placeholder={`My ${strings.project.singular}`}
defaultValue={project.name}
onChange={name => models.project.update(project, { name })}
onChange={event => models.project.update(project, { name: event.target.value })}
/>
)}
</label>
@@ -6,7 +6,6 @@ import * as models from '../../../models';
import type { RequestGroup } from '../../../models/request-group';
import type { Workspace } from '../../../models/workspace';
import { selectWorkspacesForActiveProject } from '../../redux/selectors';
import { DebouncedInput } from '../base/debounced-input';
import { type ModalHandle, Modal, ModalProps } from '../base/modal';
import { ModalBody } from '../base/modal-body';
import { ModalHeader } from '../base/modal-header';
@@ -125,14 +124,12 @@ export const RequestGroupSettingsModal = forwardRef<RequestGroupSettingsModalHan
<div className="form-control form-control--outlined">
<label>
Name
<DebouncedInput
delay={500}
// @ts-expect-error -- TSCONVERSION props expand into an input but are difficult to type
<input
type="text"
placeholder={requestGroup.name || 'My Folder'}
defaultValue={requestGroup.name}
onChange={async name => {
const updatedRequestGroup = await models.requestGroup.update(requestGroup, { name });
onChange={async event => {
const updatedRequestGroup = await models.requestGroup.update(requestGroup, { name: event.target.value });
setState(state => ({ ...state, requestGroup: updatedRequestGroup }));
}}
/>
@@ -8,7 +8,6 @@ import * as requestOperations from '../../../models/helpers/request-operations';
import type { Request } from '../../../models/request';
import { isWorkspace, Workspace } from '../../../models/workspace';
import { selectWorkspacesForActiveProject } from '../../redux/selectors';
import { DebouncedInput } from '../base/debounced-input';
import { type ModalHandle, Modal, ModalProps } from '../base/modal';
import { ModalBody } from '../base/modal-body';
import { ModalHeader } from '../base/modal-header';
@@ -123,14 +122,12 @@ export const RequestSettingsModal = forwardRef<RequestSettingsModalHandle, Modal
<label>
Name{' '}
<span className="txt-sm faint italic">(also rename by double-clicking in sidebar)</span>
<DebouncedInput
delay={500}
// @ts-expect-error -- TSCONVERSION props expand into an input but are difficult to type
<input
type="text"
placeholder={request.url || 'My Request'}
defaultValue={request.name}
onChange={async name => {
const updatedRequest = await requestOperations.update(request, { name });
onChange={async event => {
const updatedRequest = await requestOperations.update(request, { name: event.target.value });
setState(state => ({
...state,
request: updatedRequest,
@@ -12,7 +12,6 @@ import * as models from '../../../models/index';
import { isRequest } from '../../../models/request';
import { setActiveActivity } from '../../redux/modules/global';
import { selectActiveApiSpec, selectActiveWorkspace, selectActiveWorkspaceClientCertificates, selectActiveWorkspaceName } from '../../redux/selectors';
import { DebouncedInput } from '../base/debounced-input';
import { FileInputButton } from '../base/file-input-button';
import { type ModalHandle, Modal, ModalProps } from '../base/modal';
import { ModalBody } from '../base/modal-body';
@@ -234,13 +233,11 @@ export const WorkspaceSettingsModal = forwardRef<WorkspaceSettingsModalHandle, M
<div className="form-control form-control--outlined">
<label>
Name
<DebouncedInput
// @ts-expect-error -- TSCONVERSION props are spread into an input element
<input
type="text"
delay={500}
placeholder="Awesome API"
defaultValue={activeWorkspaceName}
onChange={name => workspaceOperations.rename(name, workspace, apiSpec)}
onChange={event => workspaceOperations.rename(event.target.value, workspace, apiSpec)}
/>
</label>
</div>