mirror of
https://github.com/Kong/insomnia.git
synced 2026-10-28 01:29:28 -04:00
Remove/debounced-input (#5308)
* first pass * remove debounced input * fix onPaste type errors
This commit is contained in:
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>
|
||||
|
||||
Reference in new issue
Block a user