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.get(fieldKey).clear();
|
||||
cy.get(fieldKey).type(fieldValue);
|
||||
// wait a little bit after typing for the debounce to take effect
|
||||
cy.wait(100);
|
||||
cy.contains('Submit').click();
|
||||
};
|
||||
|
||||
|
|
|
@ -87,6 +87,14 @@ export default function BaseInputTemplate<
|
|||
100
|
||||
);
|
||||
|
||||
const addDebouncedOnChangeText = useDebouncedCallback(
|
||||
(fullObject: React.ChangeEvent<HTMLInputElement>) => {
|
||||
(onChangeOverride || _onChange)(fullObject);
|
||||
},
|
||||
// delay in ms
|
||||
100
|
||||
);
|
||||
|
||||
let enableCounter = false;
|
||||
let maxCount = undefined;
|
||||
if (options && options.counter) {
|
||||
|
@ -180,9 +188,9 @@ export default function BaseInputTemplate<
|
|||
invalid={commonAttributes.invalid}
|
||||
invalidText={commonAttributes.errorMessageForField}
|
||||
autoFocus={autofocus}
|
||||
onChange={onChangeOverride || _onChange}
|
||||
disabled={disabled || readonly}
|
||||
value={value || value === 0 ? value : ''}
|
||||
defaultValue={value || value === 0 ? value : ''}
|
||||
onChange={addDebouncedOnChangeText}
|
||||
onBlur={_onBlur}
|
||||
onFocus={_onFocus}
|
||||
enableCounter={enableCounter}
|
||||
|
|
|
@ -1,140 +1,18 @@
|
|||
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';
|
||||
|
||||
// this guessType, asNumber, and processSelectValue code is pulled from rjsf/utils version 5.0.0-beta.20
|
||||
// the function was removed.
|
||||
/** Given a specific `value` attempts to guess the type of a schema element. In the case where we have to implicitly
|
||||
* create a schema, it is useful to know what type to use based on the data we are defining.
|
||||
*
|
||||
* @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({
|
||||
function SelectWidget<
|
||||
T = any,
|
||||
S extends StrictRJSFSchema = RJSFSchema,
|
||||
F extends FormContextType = any
|
||||
>({
|
||||
schema,
|
||||
id,
|
||||
options,
|
||||
|
@ -151,22 +29,21 @@ function SelectWidget({
|
|||
uiSchema,
|
||||
placeholder,
|
||||
rawErrors = [],
|
||||
}: WidgetProps) {
|
||||
}: WidgetProps<T, S, F>) {
|
||||
const { enumOptions } = options;
|
||||
let { enumDisabled } = options;
|
||||
|
||||
const emptyValue = multiple ? [] : '';
|
||||
|
||||
const _onChange = ({
|
||||
target: { value },
|
||||
}: React.ChangeEvent<{ name?: string; value: unknown }>) =>
|
||||
onChange(processSelectValue(schema, value, options));
|
||||
const _onBlur = ({ target: { value } }: React.FocusEvent<HTMLInputElement>) =>
|
||||
onBlur(id, processSelectValue(schema, value, options));
|
||||
const _onFocus = ({
|
||||
target: { value },
|
||||
}: React.FocusEvent<HTMLInputElement>) =>
|
||||
onFocus(id, processSelectValue(schema, value, options));
|
||||
const _onChange = ({ target: { value } }: ChangeEvent<{ value: string }>) => {
|
||||
onChange(value);
|
||||
};
|
||||
|
||||
const _onBlur = ({ target: { value } }: FocusEvent<HTMLInputElement>) => {
|
||||
onBlur(id, value);
|
||||
};
|
||||
const _onFocus = ({ target: { value } }: FocusEvent<HTMLInputElement>) =>
|
||||
onFocus(id, value);
|
||||
|
||||
const commonAttributes = getCommonAttributes(
|
||||
label,
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
import React, { FocusEvent, useCallback } from 'react';
|
||||
// @ts-ignore
|
||||
import { TextArea } from '@carbon/react';
|
||||
import { useDebouncedCallback } from 'use-debounce';
|
||||
import {
|
||||
FormContextType,
|
||||
RJSFSchema,
|
||||
|
@ -52,6 +53,17 @@ function TextareaWidget<
|
|||
[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(
|
||||
label,
|
||||
schema,
|
||||
|
@ -78,7 +90,7 @@ function TextareaWidget<
|
|||
name={id}
|
||||
className="text-input"
|
||||
helperText={commonAttributes.helperText}
|
||||
value={value || ''}
|
||||
defaultValue={value || ''}
|
||||
labelText=""
|
||||
placeholder={placeholder}
|
||||
required={required}
|
||||
|
@ -88,7 +100,7 @@ function TextareaWidget<
|
|||
rows={options.rows}
|
||||
onBlur={handleBlur}
|
||||
onFocus={handleFocus}
|
||||
onChange={handleChange}
|
||||
onChange={addDebouncedOnChangeText}
|
||||
invalid={commonAttributes.invalid}
|
||||
invalidText={commonAttributes.errorMessageForField}
|
||||
enableCounter={enableCounter}
|
||||
|
|
Loading…
Reference in New Issue