fixed the select widget and text fields so they are no longer so slow when typing (#1573)
* fixed the select widget and text fields so they are no longer so slow when typing * attempt to wait a little after typing in cypress tests for the debounce w/ burnettk * fixed onChangeOverride issue w/ burnettk --------- Co-authored-by: jasquat <jasquat@users.noreply.github.com>
This commit is contained in:
parent
e5d51907fe
commit
154be22161
|
@ -2,6 +2,8 @@ const submitInputIntoFormField = (taskName, fieldKey, fieldValue) => {
|
||||||
cy.contains(`Task: ${taskName}`, { timeout: 10000 });
|
cy.contains(`Task: ${taskName}`, { timeout: 10000 });
|
||||||
cy.get(fieldKey).clear();
|
cy.get(fieldKey).clear();
|
||||||
cy.get(fieldKey).type(fieldValue);
|
cy.get(fieldKey).type(fieldValue);
|
||||||
|
// wait a little bit after typing for the debounce to take effect
|
||||||
|
cy.wait(100);
|
||||||
cy.contains('Submit').click();
|
cy.contains('Submit').click();
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
@ -87,6 +87,14 @@ export default function BaseInputTemplate<
|
||||||
100
|
100
|
||||||
);
|
);
|
||||||
|
|
||||||
|
const addDebouncedOnChangeText = useDebouncedCallback(
|
||||||
|
(fullObject: React.ChangeEvent<HTMLInputElement>) => {
|
||||||
|
(onChangeOverride || _onChange)(fullObject);
|
||||||
|
},
|
||||||
|
// delay in ms
|
||||||
|
100
|
||||||
|
);
|
||||||
|
|
||||||
let enableCounter = false;
|
let enableCounter = false;
|
||||||
let maxCount = undefined;
|
let maxCount = undefined;
|
||||||
if (options && options.counter) {
|
if (options && options.counter) {
|
||||||
|
@ -180,9 +188,9 @@ export default function BaseInputTemplate<
|
||||||
invalid={commonAttributes.invalid}
|
invalid={commonAttributes.invalid}
|
||||||
invalidText={commonAttributes.errorMessageForField}
|
invalidText={commonAttributes.errorMessageForField}
|
||||||
autoFocus={autofocus}
|
autoFocus={autofocus}
|
||||||
onChange={onChangeOverride || _onChange}
|
|
||||||
disabled={disabled || readonly}
|
disabled={disabled || readonly}
|
||||||
value={value || value === 0 ? value : ''}
|
defaultValue={value || value === 0 ? value : ''}
|
||||||
|
onChange={addDebouncedOnChangeText}
|
||||||
onBlur={_onBlur}
|
onBlur={_onBlur}
|
||||||
onFocus={_onFocus}
|
onFocus={_onFocus}
|
||||||
enableCounter={enableCounter}
|
enableCounter={enableCounter}
|
||||||
|
|
|
@ -1,140 +1,18 @@
|
||||||
import { Select, SelectItem } from '@carbon/react';
|
import { Select, SelectItem } from '@carbon/react';
|
||||||
import { WidgetProps } from '@rjsf/utils';
|
import {
|
||||||
|
FormContextType,
|
||||||
|
RJSFSchema,
|
||||||
|
StrictRJSFSchema,
|
||||||
|
WidgetProps,
|
||||||
|
} from '@rjsf/utils';
|
||||||
|
import { ChangeEvent, FocusEvent } from 'react';
|
||||||
import { getCommonAttributes } from '../../helpers';
|
import { getCommonAttributes } from '../../helpers';
|
||||||
|
|
||||||
// this guessType, asNumber, and processSelectValue code is pulled from rjsf/utils version 5.0.0-beta.20
|
function SelectWidget<
|
||||||
// the function was removed.
|
T = any,
|
||||||
/** Given a specific `value` attempts to guess the type of a schema element. In the case where we have to implicitly
|
S extends StrictRJSFSchema = RJSFSchema,
|
||||||
* create a schema, it is useful to know what type to use based on the data we are defining.
|
F extends FormContextType = any
|
||||||
*
|
>({
|
||||||
* @param value - The value from which to guess the type
|
|
||||||
* @returns - The best guess for the object type
|
|
||||||
*/
|
|
||||||
function guessType(value) {
|
|
||||||
if (Array.isArray(value)) {
|
|
||||||
return 'array';
|
|
||||||
}
|
|
||||||
if (typeof value === 'string') {
|
|
||||||
return 'string';
|
|
||||||
}
|
|
||||||
if (value == null) {
|
|
||||||
return 'null';
|
|
||||||
}
|
|
||||||
if (typeof value === 'boolean') {
|
|
||||||
return 'boolean';
|
|
||||||
}
|
|
||||||
if (!isNaN(value)) {
|
|
||||||
return 'number';
|
|
||||||
}
|
|
||||||
if (typeof value === 'object') {
|
|
||||||
return 'object';
|
|
||||||
}
|
|
||||||
// Default to string if we can't figure it out
|
|
||||||
return 'string';
|
|
||||||
}
|
|
||||||
|
|
||||||
/** Attempts to convert the string into a number. If an empty string is provided, then `undefined` is returned. If a
|
|
||||||
* `null` is provided, it is returned. If the string ends in a `.` then the string is returned because the user may be
|
|
||||||
* in the middle of typing a float number. If a number ends in a pattern like `.0`, `.20`, `.030`, string is returned
|
|
||||||
* because the user may be typing number that will end in a non-zero digit. Otherwise, the string is wrapped by
|
|
||||||
* `Number()` and if that result is not `NaN`, that number will be returned, otherwise the string `value` will be.
|
|
||||||
*
|
|
||||||
* @param value - The string or null value to convert to a number
|
|
||||||
* @returns - The `value` converted to a number when appropriate, otherwise the `value`
|
|
||||||
*/
|
|
||||||
function asNumber(value) {
|
|
||||||
if (value === '') {
|
|
||||||
return undefined;
|
|
||||||
}
|
|
||||||
if (value === null) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
if (/\.$/.test(value)) {
|
|
||||||
// '3.' can't really be considered a number even if it parses in js. The
|
|
||||||
// user is most likely entering a float.
|
|
||||||
return value;
|
|
||||||
}
|
|
||||||
if (/\.0$/.test(value)) {
|
|
||||||
// we need to return this as a string here, to allow for input like 3.07
|
|
||||||
return value;
|
|
||||||
}
|
|
||||||
if (/\.\d*0$/.test(value)) {
|
|
||||||
// It's a number, that's cool - but we need it as a string so it doesn't screw
|
|
||||||
// with the user when entering dollar amounts or other values (such as those with
|
|
||||||
// specific precision or number of significant digits)
|
|
||||||
return value;
|
|
||||||
}
|
|
||||||
var n = Number(value);
|
|
||||||
var valid = typeof n === 'number' && !Number.isNaN(n);
|
|
||||||
return valid ? n : value;
|
|
||||||
}
|
|
||||||
|
|
||||||
function get(object, path) {
|
|
||||||
// Split the path into an array of keys
|
|
||||||
const keys = Array.isArray(path) ? path : path.split('.');
|
|
||||||
|
|
||||||
// Traverse the object along the keys
|
|
||||||
let result = object;
|
|
||||||
for (let key of keys) {
|
|
||||||
if (result == null) {
|
|
||||||
return undefined; // If the path does not exist, return undefined
|
|
||||||
}
|
|
||||||
result = result[key];
|
|
||||||
}
|
|
||||||
|
|
||||||
return result; // Return the found value or undefined if not found
|
|
||||||
}
|
|
||||||
|
|
||||||
var nums = /*#__PURE__*/ new Set(['number', 'integer']);
|
|
||||||
/** Returns the real value for a select widget due to a silly limitation in the DOM which causes option change event
|
|
||||||
* values to always be retrieved as strings. Uses the `schema` to help determine the value's true type. If the value is
|
|
||||||
* an empty string, then the `emptyValue` from the `options` is returned, falling back to undefined.
|
|
||||||
*
|
|
||||||
* @param schema - The schema to used to determine the value's true type
|
|
||||||
* @param [value] - The value to convert
|
|
||||||
* @param [options] - The UIOptionsType from which to potentially extract the emptyValue
|
|
||||||
* @returns - The `value` converted to the proper type
|
|
||||||
*/
|
|
||||||
function processSelectValue(schema, value, options) {
|
|
||||||
var schemaEnum = schema['enum'],
|
|
||||||
type = schema.type,
|
|
||||||
items = schema.items;
|
|
||||||
if (value === '') {
|
|
||||||
return options && options.emptyValue !== undefined
|
|
||||||
? options.emptyValue
|
|
||||||
: undefined;
|
|
||||||
}
|
|
||||||
if (type === 'array' && items && nums.has(get(items, 'type'))) {
|
|
||||||
return value.map(asNumber);
|
|
||||||
}
|
|
||||||
if (type === 'boolean') {
|
|
||||||
return value === 'true';
|
|
||||||
}
|
|
||||||
if (nums.has(type)) {
|
|
||||||
return asNumber(value);
|
|
||||||
}
|
|
||||||
// If type is undefined, but an enum is present, try and infer the type from
|
|
||||||
// the enum values
|
|
||||||
if (Array.isArray(schemaEnum)) {
|
|
||||||
if (
|
|
||||||
schemaEnum.every(function (x) {
|
|
||||||
return nums.has(guessType(x));
|
|
||||||
})
|
|
||||||
) {
|
|
||||||
return asNumber(value);
|
|
||||||
}
|
|
||||||
if (
|
|
||||||
schemaEnum.every(function (x) {
|
|
||||||
return guessType(x) === 'boolean';
|
|
||||||
})
|
|
||||||
) {
|
|
||||||
return value === 'true';
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return value;
|
|
||||||
}
|
|
||||||
|
|
||||||
function SelectWidget({
|
|
||||||
schema,
|
schema,
|
||||||
id,
|
id,
|
||||||
options,
|
options,
|
||||||
|
@ -151,22 +29,21 @@ function SelectWidget({
|
||||||
uiSchema,
|
uiSchema,
|
||||||
placeholder,
|
placeholder,
|
||||||
rawErrors = [],
|
rawErrors = [],
|
||||||
}: WidgetProps) {
|
}: WidgetProps<T, S, F>) {
|
||||||
const { enumOptions } = options;
|
const { enumOptions } = options;
|
||||||
let { enumDisabled } = options;
|
let { enumDisabled } = options;
|
||||||
|
|
||||||
const emptyValue = multiple ? [] : '';
|
const emptyValue = multiple ? [] : '';
|
||||||
|
|
||||||
const _onChange = ({
|
const _onChange = ({ target: { value } }: ChangeEvent<{ value: string }>) => {
|
||||||
target: { value },
|
onChange(value);
|
||||||
}: React.ChangeEvent<{ name?: string; value: unknown }>) =>
|
};
|
||||||
onChange(processSelectValue(schema, value, options));
|
|
||||||
const _onBlur = ({ target: { value } }: React.FocusEvent<HTMLInputElement>) =>
|
const _onBlur = ({ target: { value } }: FocusEvent<HTMLInputElement>) => {
|
||||||
onBlur(id, processSelectValue(schema, value, options));
|
onBlur(id, value);
|
||||||
const _onFocus = ({
|
};
|
||||||
target: { value },
|
const _onFocus = ({ target: { value } }: FocusEvent<HTMLInputElement>) =>
|
||||||
}: React.FocusEvent<HTMLInputElement>) =>
|
onFocus(id, value);
|
||||||
onFocus(id, processSelectValue(schema, value, options));
|
|
||||||
|
|
||||||
const commonAttributes = getCommonAttributes(
|
const commonAttributes = getCommonAttributes(
|
||||||
label,
|
label,
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
import React, { FocusEvent, useCallback } from 'react';
|
import React, { FocusEvent, useCallback } from 'react';
|
||||||
// @ts-ignore
|
// @ts-ignore
|
||||||
import { TextArea } from '@carbon/react';
|
import { TextArea } from '@carbon/react';
|
||||||
|
import { useDebouncedCallback } from 'use-debounce';
|
||||||
import {
|
import {
|
||||||
FormContextType,
|
FormContextType,
|
||||||
RJSFSchema,
|
RJSFSchema,
|
||||||
|
@ -52,6 +53,17 @@ function TextareaWidget<
|
||||||
[id, onFocus]
|
[id, onFocus]
|
||||||
);
|
);
|
||||||
|
|
||||||
|
// this helps with performance for the select widget with rsjf 5.1+.
|
||||||
|
// otherwise if the form has an enum with a corresponding oneOf, after choosing
|
||||||
|
// an option in the dropdown, the text area slows way down.
|
||||||
|
const addDebouncedOnChangeText = useDebouncedCallback(
|
||||||
|
(fullObject: React.ChangeEvent<HTMLTextAreaElement>) => {
|
||||||
|
handleChange(fullObject);
|
||||||
|
},
|
||||||
|
// delay in ms
|
||||||
|
100
|
||||||
|
);
|
||||||
|
|
||||||
const commonAttributes = getCommonAttributes(
|
const commonAttributes = getCommonAttributes(
|
||||||
label,
|
label,
|
||||||
schema,
|
schema,
|
||||||
|
@ -78,7 +90,7 @@ function TextareaWidget<
|
||||||
name={id}
|
name={id}
|
||||||
className="text-input"
|
className="text-input"
|
||||||
helperText={commonAttributes.helperText}
|
helperText={commonAttributes.helperText}
|
||||||
value={value || ''}
|
defaultValue={value || ''}
|
||||||
labelText=""
|
labelText=""
|
||||||
placeholder={placeholder}
|
placeholder={placeholder}
|
||||||
required={required}
|
required={required}
|
||||||
|
@ -88,7 +100,7 @@ function TextareaWidget<
|
||||||
rows={options.rows}
|
rows={options.rows}
|
||||||
onBlur={handleBlur}
|
onBlur={handleBlur}
|
||||||
onFocus={handleFocus}
|
onFocus={handleFocus}
|
||||||
onChange={handleChange}
|
onChange={addDebouncedOnChangeText}
|
||||||
invalid={commonAttributes.invalid}
|
invalid={commonAttributes.invalid}
|
||||||
invalidText={commonAttributes.errorMessageForField}
|
invalidText={commonAttributes.errorMessageForField}
|
||||||
enableCounter={enableCounter}
|
enableCounter={enableCounter}
|
||||||
|
|
Loading…
Reference in New Issue