chore: Adding debounce to onValueChange for input widgets (#40849)
## Description Adding debounce to `onValueChange` for input widgets to fix multiple Execute API calls happening in reactive queries flow. Fixes [#40813](https://github.com/appsmithorg/appsmith/issues/40813) ## Automation /ok-to-test tags="@tag.All" ### 🔍 Cypress test results <!-- This is an auto-generated comment: Cypress test results --> > [!TIP] > 🟢 🟢 🟢 All cypress tests have passed! 🎉 🎉 🎉 > Workflow run: <https://github.com/appsmithorg/appsmith/actions/runs/15486342735> > Commit: 6943ba5d0df915256cf29831df53e9ff9880d617 > <a href="https://internal.appsmith.com/app/cypress-dashboard/rundetails-65890b3c81d7400d08fa9ee5?branch=master&workflowId=15486342735&attempt=1" target="_blank">Cypress dashboard</a>. > Tags: `@tag.All` > Spec: > <hr>Fri, 06 Jun 2025 09:40:52 UTC <!-- end of auto-generated comment: Cypress test results --> ## Communication Should the DevRel and Marketing teams inform users about this change? - [ ] Yes - [ ] No <!-- This is an auto-generated comment: release notes by coderabbit.ai --> ## Summary by CodeRabbit - **New Features** - Input widgets now update their displayed values instantly while saving changes in the background with a short delay, improving typing responsiveness. - Input changes are grouped and saved after a brief pause, reducing unnecessary updates and enhancing performance. - **Bug Fixes** - Input fields now stay in sync with external updates and clear any pending background updates when needed, preventing outdated or duplicate changes. - **Chores** - Improved cleanup of background processes when input widgets are removed, ensuring smoother operation. - **Tests** - Added typing delays in input simulation during Cypress tests to better mimic real user input timing. <!-- end of auto-generated comment: release notes by coderabbit.ai -->
This commit is contained in:
parent
9c855e36a9
commit
fde8d013aa
|
|
@ -1,3 +1,4 @@
|
|||
import { DEBOUNCE_WAIT_TIME_ON_INPUT_CHANGE } from "../../../../../../src/constants/WidgetConstants";
|
||||
import { agHelper } from "../../../../../support/Objects/ObjectsCore";
|
||||
|
||||
const widgetName = "inputwidgetv2";
|
||||
|
|
@ -385,7 +386,9 @@ describe(
|
|||
cy.get(widgetInput).clear();
|
||||
cy.wait(300);
|
||||
// Input text and hit enter key
|
||||
cy.get(widgetInput).type("test{enter}");
|
||||
cy.get(widgetInput).type("test{enter}", {
|
||||
delay: DEBOUNCE_WAIT_TIME_ON_INPUT_CHANGE,
|
||||
});
|
||||
// Assert if the Text widget contains the whole value, test
|
||||
cy.get(".t--widget-textwidget").should("have.text", "test");
|
||||
});
|
||||
|
|
|
|||
|
|
@ -1,6 +1,7 @@
|
|||
const publishLocators = require("../../../../../locators/publishWidgetspage.json");
|
||||
const commonlocators = require("../../../../../locators/commonlocators.json");
|
||||
|
||||
import { DEBOUNCE_WAIT_TIME_ON_INPUT_CHANGE } from "../../../../../../src/constants/WidgetConstants";
|
||||
import * as _ from "../../../../../support/Objects/ObjectsCore";
|
||||
|
||||
const widgetSelector = (name) => `[data-widgetname-cy="${name}"]`;
|
||||
|
|
@ -76,7 +77,7 @@ describe(
|
|||
cy.get(".t--draggable-inputwidgetv2").each(($inputWidget, index) => {
|
||||
cy.wrap($inputWidget)
|
||||
.find("input")
|
||||
.type(index + 1);
|
||||
.type(index + 1, { delay: DEBOUNCE_WAIT_TIME_ON_INPUT_CHANGE });
|
||||
});
|
||||
|
||||
// Verify the typed value
|
||||
|
|
@ -101,7 +102,7 @@ describe(
|
|||
cy.get(".t--draggable-inputwidgetv2").each(($inputWidget, index) => {
|
||||
cy.wrap($inputWidget)
|
||||
.find("input")
|
||||
.type(index + 4);
|
||||
.type(index + 4, { delay: DEBOUNCE_WAIT_TIME_ON_INPUT_CHANGE });
|
||||
});
|
||||
|
||||
// Verify the typed value
|
||||
|
|
|
|||
|
|
@ -1,3 +1,4 @@
|
|||
import { DEBOUNCE_WAIT_TIME_ON_INPUT_CHANGE } from "../../../../../../src/constants/WidgetConstants";
|
||||
const nestedListDSL = require("../../../../../fixtures/Listv2/nestedList.json");
|
||||
const commonlocators = require("../../../../../locators/commonlocators.json");
|
||||
|
||||
|
|
@ -22,10 +23,14 @@ describe(
|
|||
"{{showAlert(`${level_1.currentView.Text1.text} _ ${level_1.currentItem.id} _ ${level_1.currentIndex} _ ${level_1.currentView.Input1.text} _ ${currentView.Input2.text}`)}}",
|
||||
);
|
||||
// Enter text in the parent list widget's text input
|
||||
cy.get(widgetSelector("Input1")).find("input").type("outer input");
|
||||
cy.get(widgetSelector("Input1"))
|
||||
.find("input")
|
||||
.type("outer input", { delay: DEBOUNCE_WAIT_TIME_ON_INPUT_CHANGE });
|
||||
|
||||
// Enter text in the child list widget's text input in first row
|
||||
cy.get(widgetSelector("Input2")).find("input").type("inner input");
|
||||
cy.get(widgetSelector("Input2"))
|
||||
.find("input")
|
||||
.type("inner input", { delay: DEBOUNCE_WAIT_TIME_ON_INPUT_CHANGE });
|
||||
|
||||
// click the button on inner list 1st row.
|
||||
cy.get(widgetSelector("Button3")).find("button").click({ force: true });
|
||||
|
|
@ -40,13 +45,17 @@ describe(
|
|||
cy.get(widgetSelector("Input1"))
|
||||
.find("input")
|
||||
.clear()
|
||||
.type("outer input updated");
|
||||
.type("outer input updated", {
|
||||
delay: DEBOUNCE_WAIT_TIME_ON_INPUT_CHANGE,
|
||||
});
|
||||
|
||||
// Enter text in the child list widget's text input in first row
|
||||
cy.get(widgetSelector("Input2"))
|
||||
.find("input")
|
||||
.clear()
|
||||
.type("inner input updated");
|
||||
.type("inner input updated", {
|
||||
delay: DEBOUNCE_WAIT_TIME_ON_INPUT_CHANGE,
|
||||
});
|
||||
|
||||
// click the button on inner list 1st row.
|
||||
cy.get(widgetSelector("Button3")).find("button").click({ force: true });
|
||||
|
|
|
|||
|
|
@ -7,6 +7,7 @@ import { EntityItems } from "./AssertHelper";
|
|||
import EditorNavigator from "./EditorNavigation";
|
||||
import { EntityType } from "./EditorNavigation";
|
||||
import ClickOptions = Cypress.ClickOptions;
|
||||
import { DEBOUNCE_WAIT_TIME_ON_INPUT_CHANGE } from "../../../src/constants/WidgetConstants";
|
||||
|
||||
type ElementType = string | JQuery<HTMLElement>;
|
||||
|
||||
|
|
@ -945,10 +946,13 @@ export class AggregateHelper {
|
|||
.focus()
|
||||
.type("{backspace}".repeat(charCount), { timeout: 2, force: true })
|
||||
.wait(50)
|
||||
.type(totype);
|
||||
.type(totype, { delay: DEBOUNCE_WAIT_TIME_ON_INPUT_CHANGE });
|
||||
else {
|
||||
if (charCount == -1) this.GetElement(selector).eq(index).clear();
|
||||
this.TypeText(selector, totype, index);
|
||||
this.TypeText(selector, totype, {
|
||||
index,
|
||||
delay: DEBOUNCE_WAIT_TIME_ON_INPUT_CHANGE,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -973,7 +977,10 @@ export class AggregateHelper {
|
|||
force = false,
|
||||
) {
|
||||
this.ClearTextField(selector, force, index);
|
||||
return this.TypeText(selector, totype, index);
|
||||
return this.TypeText(selector, totype, {
|
||||
index,
|
||||
delay: DEBOUNCE_WAIT_TIME_ON_INPUT_CHANGE,
|
||||
});
|
||||
}
|
||||
|
||||
public TypeText(
|
||||
|
|
@ -1323,7 +1330,10 @@ export class AggregateHelper {
|
|||
toClear && this.ClearInputText(name);
|
||||
cy.xpath(this.locator._inputWidgetValueField(name, isInput))
|
||||
.trigger("click")
|
||||
.type(input, { parseSpecialCharSequences: false });
|
||||
.type(input, {
|
||||
parseSpecialCharSequences: false,
|
||||
delay: DEBOUNCE_WAIT_TIME_ON_INPUT_CHANGE,
|
||||
});
|
||||
}
|
||||
|
||||
public ClearInputText(name: string, isInput = true) {
|
||||
|
|
|
|||
|
|
@ -279,3 +279,6 @@ export type PasteWidgetReduxAction = {
|
|||
groupWidgets: boolean;
|
||||
existingWidgets?: unknown;
|
||||
} & EitherMouseLocationORGridPosition;
|
||||
|
||||
// Constant for debouncing the input change to avoid multiple Execute calls in reactive flow
|
||||
export const DEBOUNCE_WAIT_TIME_ON_INPUT_CHANGE = 300;
|
||||
|
|
|
|||
|
|
@ -12,7 +12,7 @@ import {
|
|||
getCountryCodeFromCurrencyCode,
|
||||
} from "../component/CurrencyCodeDropdown";
|
||||
import { AutocompleteDataType } from "utils/autocomplete/AutocompleteDataType";
|
||||
import _ from "lodash";
|
||||
import _, { debounce } from "lodash";
|
||||
import derivedProperties from "./parsedDerivedProperties";
|
||||
import BaseInputWidget from "widgets/BaseInputWidget";
|
||||
import type { BaseInputWidgetProps } from "widgets/BaseInputWidget/widget";
|
||||
|
|
@ -42,7 +42,10 @@ import { DynamicHeight } from "utils/WidgetFeatures";
|
|||
import { getDefaultCurrency } from "../component/CurrencyCodeDropdown";
|
||||
import IconSVG from "../icon.svg";
|
||||
import ThumbnailSVG from "../thumbnail.svg";
|
||||
import { WIDGET_TAGS } from "constants/WidgetConstants";
|
||||
import {
|
||||
DEBOUNCE_WAIT_TIME_ON_INPUT_CHANGE,
|
||||
WIDGET_TAGS,
|
||||
} from "constants/WidgetConstants";
|
||||
import { appsmithTelemetry } from "instrumentation";
|
||||
|
||||
export function defaultValueValidation(
|
||||
|
|
@ -154,10 +157,19 @@ export function defaultValueValidation(
|
|||
};
|
||||
}
|
||||
|
||||
interface CurrencyInputWidgetState extends WidgetState {
|
||||
inputValue: string;
|
||||
}
|
||||
|
||||
class CurrencyInputWidget extends BaseInputWidget<
|
||||
CurrencyInputWidgetProps,
|
||||
WidgetState
|
||||
CurrencyInputWidgetState
|
||||
> {
|
||||
constructor(props: CurrencyInputWidgetProps) {
|
||||
super(props);
|
||||
this.state = { inputValue: props.text ?? "" };
|
||||
}
|
||||
|
||||
static type = "CURRENCY_INPUT_WIDGET";
|
||||
|
||||
static getConfig() {
|
||||
|
|
@ -457,6 +469,12 @@ class CurrencyInputWidget extends BaseInputWidget<
|
|||
}
|
||||
|
||||
componentDidUpdate(prevProps: CurrencyInputWidgetProps) {
|
||||
if (prevProps.text !== this.props.text) {
|
||||
this.setState({ inputValue: this.props.text ?? "" });
|
||||
// Cancel any pending debounced calls when value is updated externally
|
||||
this.debouncedOnValueChange.cancel();
|
||||
}
|
||||
|
||||
if (
|
||||
prevProps.text !== this.props.text &&
|
||||
!this.props.isFocused &&
|
||||
|
|
@ -481,6 +499,10 @@ class CurrencyInputWidget extends BaseInputWidget<
|
|||
}
|
||||
}
|
||||
|
||||
componentWillUnmount() {
|
||||
this.debouncedOnValueChange.cancel();
|
||||
}
|
||||
|
||||
formatText() {
|
||||
if (!!this.props.text && !this.isTextFormatted()) {
|
||||
try {
|
||||
|
|
@ -506,6 +528,22 @@ class CurrencyInputWidget extends BaseInputWidget<
|
|||
}
|
||||
}
|
||||
|
||||
// debouncing the input change to avoid multiple Execute calls in reactive flow
|
||||
debouncedOnValueChange = debounce((value: string) => {
|
||||
// text is stored as what user has typed
|
||||
this.props.updateWidgetMetaProperty("text", String(value), {
|
||||
triggerPropertyName: "onTextChanged",
|
||||
dynamicString: this.props.onTextChanged,
|
||||
event: {
|
||||
type: EventType.ON_TEXT_CHANGE,
|
||||
},
|
||||
});
|
||||
|
||||
if (!this.props.isDirty) {
|
||||
this.props.updateWidgetMetaProperty("isDirty", true);
|
||||
}
|
||||
}, DEBOUNCE_WAIT_TIME_ON_INPUT_CHANGE);
|
||||
|
||||
onValueChange = (value: string) => {
|
||||
let formattedValue = "";
|
||||
const decimalSeperator = getLocaleDecimalSeperator();
|
||||
|
|
@ -524,18 +562,8 @@ class CurrencyInputWidget extends BaseInputWidget<
|
|||
});
|
||||
}
|
||||
|
||||
// text is stored as what user has typed
|
||||
this.props.updateWidgetMetaProperty("text", String(formattedValue), {
|
||||
triggerPropertyName: "onTextChanged",
|
||||
dynamicString: this.props.onTextChanged,
|
||||
event: {
|
||||
type: EventType.ON_TEXT_CHANGE,
|
||||
},
|
||||
});
|
||||
|
||||
if (!this.props.isDirty) {
|
||||
this.props.updateWidgetMetaProperty("isDirty", true);
|
||||
}
|
||||
this.setState({ inputValue: formattedValue });
|
||||
this.debouncedOnValueChange(formattedValue);
|
||||
};
|
||||
|
||||
isTextFormatted = () => {
|
||||
|
|
@ -623,7 +651,7 @@ class CurrencyInputWidget extends BaseInputWidget<
|
|||
};
|
||||
|
||||
getWidgetView() {
|
||||
const value = this.props.text ?? "";
|
||||
const value = this.state.inputValue ?? "";
|
||||
const isInvalid =
|
||||
"isValid" in this.props && !this.props.isValid && !!this.props.isDirty;
|
||||
const currencyCode = this.props.currencyCode;
|
||||
|
|
|
|||
|
|
@ -18,6 +18,7 @@ import type { ExecutionResult } from "constants/AppsmithActionConstants/ActionCo
|
|||
import { EventType } from "constants/AppsmithActionConstants/ActionConstants";
|
||||
import type { TextSize } from "constants/WidgetConstants";
|
||||
import {
|
||||
DEBOUNCE_WAIT_TIME_ON_INPUT_CHANGE,
|
||||
GridDefaults,
|
||||
RenderModes,
|
||||
WIDGET_TAGS,
|
||||
|
|
@ -47,6 +48,7 @@ import {
|
|||
import type { InputType } from "../constants";
|
||||
import { InputTypes } from "../constants";
|
||||
import IconSVG from "../icon.svg";
|
||||
import { debounce } from "lodash";
|
||||
|
||||
export function defaultValueValidation(
|
||||
// TODO: Fix this the next time the file is edited
|
||||
|
|
@ -141,14 +143,31 @@ export function defaultValueValidation(
|
|||
};
|
||||
}
|
||||
|
||||
class InputWidget extends BaseWidget<InputWidgetProps, WidgetState> {
|
||||
interface InputWidgetState extends WidgetState {
|
||||
inputValue: string;
|
||||
}
|
||||
|
||||
class InputWidget extends BaseWidget<InputWidgetProps, InputWidgetState> {
|
||||
constructor(props: InputWidgetProps) {
|
||||
super(props);
|
||||
this.state = {
|
||||
text: props.text,
|
||||
inputValue: props.text ?? "",
|
||||
};
|
||||
}
|
||||
|
||||
componentDidUpdate(prevProps: InputWidgetProps) {
|
||||
if (prevProps.text !== this.props.text) {
|
||||
this.setState({ inputValue: this.props.text ?? "" });
|
||||
// Cancel any pending debounced calls when value is updated externally
|
||||
this.debouncedOnValueChange.cancel();
|
||||
}
|
||||
}
|
||||
|
||||
componentWillUnmount() {
|
||||
this.debouncedOnValueChange.cancel();
|
||||
}
|
||||
|
||||
static type = "INPUT_WIDGET";
|
||||
|
||||
static getConfig() {
|
||||
|
|
@ -877,7 +896,8 @@ class InputWidget extends BaseWidget<InputWidgetProps, WidgetState> {
|
|||
};
|
||||
}
|
||||
|
||||
onValueChange = (value: string) => {
|
||||
// debouncing the input change to avoid multiple Execute calls in reactive flow
|
||||
debouncedOnValueChange = debounce((value: string) => {
|
||||
this.props.updateWidgetMetaProperty("text", value, {
|
||||
triggerPropertyName: "onTextChanged",
|
||||
dynamicString: this.props.onTextChanged,
|
||||
|
|
@ -889,6 +909,11 @@ class InputWidget extends BaseWidget<InputWidgetProps, WidgetState> {
|
|||
if (!this.props.isDirty) {
|
||||
this.props.updateWidgetMetaProperty("isDirty", true);
|
||||
}
|
||||
}, DEBOUNCE_WAIT_TIME_ON_INPUT_CHANGE);
|
||||
|
||||
onValueChange = (value: string) => {
|
||||
this.setState({ inputValue: value });
|
||||
this.debouncedOnValueChange(value);
|
||||
};
|
||||
|
||||
onCurrencyTypeChange = (code?: string) => {
|
||||
|
|
@ -967,12 +992,13 @@ class InputWidget extends BaseWidget<InputWidgetProps, WidgetState> {
|
|||
|
||||
getFormattedText = () => {
|
||||
if (this.props.isFocused || this.props.inputType !== InputTypes.CURRENCY) {
|
||||
return this.props.text !== undefined ? this.props.text : "";
|
||||
return this.state.inputValue !== undefined ? this.state.inputValue : "";
|
||||
}
|
||||
|
||||
if (this.props.text === "" || this.props.text === undefined) return "";
|
||||
if (this.state.inputValue === "" || this.state.inputValue === undefined)
|
||||
return "";
|
||||
|
||||
const valueToFormat = String(this.props.text);
|
||||
const valueToFormat = String(this.state.inputValue);
|
||||
|
||||
const locale = getLocale();
|
||||
const decimalSeparator = getDecimalSeparator(locale);
|
||||
|
|
|
|||
|
|
@ -18,7 +18,7 @@ import {
|
|||
INPUT_TEXT_MAX_CHAR_ERROR,
|
||||
} from "ee/constants/messages";
|
||||
import type { SetterConfig, Stylesheet } from "entities/AppTheming";
|
||||
import { isNil, isNumber, merge, toString } from "lodash";
|
||||
import { debounce, isNil, isNumber, merge, toString } from "lodash";
|
||||
import React from "react";
|
||||
import { DynamicHeight } from "utils/WidgetFeatures";
|
||||
import { AutocompleteDataType } from "utils/autocomplete/AutocompleteDataType";
|
||||
|
|
@ -48,7 +48,10 @@ import type {
|
|||
PropertyUpdates,
|
||||
SnipingModeProperty,
|
||||
} from "WidgetProvider/constants";
|
||||
import { WIDGET_TAGS } from "constants/WidgetConstants";
|
||||
import {
|
||||
DEBOUNCE_WAIT_TIME_ON_INPUT_CHANGE,
|
||||
WIDGET_TAGS,
|
||||
} from "constants/WidgetConstants";
|
||||
import { FEATURE_FLAG } from "ee/entities/FeatureFlag";
|
||||
import { ResponsiveBehavior } from "layoutSystems/common/utils/constants";
|
||||
|
||||
|
|
@ -299,11 +302,16 @@ function InputTypeUpdateHook(
|
|||
return updates;
|
||||
}
|
||||
|
||||
class InputWidget extends BaseInputWidget<InputWidgetProps, WidgetState> {
|
||||
interface InputWidgetState extends WidgetState {
|
||||
inputValue: string;
|
||||
}
|
||||
|
||||
class InputWidget extends BaseInputWidget<InputWidgetProps, InputWidgetState> {
|
||||
constructor(props: InputWidgetProps) {
|
||||
super(props);
|
||||
this.state = {
|
||||
isFocused: false,
|
||||
inputValue: props.inputText ?? "",
|
||||
};
|
||||
}
|
||||
|
||||
|
|
@ -706,6 +714,12 @@ class InputWidget extends BaseInputWidget<InputWidgetProps, WidgetState> {
|
|||
};
|
||||
|
||||
componentDidUpdate = (prevProps: InputWidgetProps) => {
|
||||
if (prevProps.inputText !== this.props.inputText) {
|
||||
this.setState({ inputValue: this.props.inputText ?? "" });
|
||||
// Cancel any pending debounced calls when value is updated externally
|
||||
this.debouncedOnValueChange.cancel();
|
||||
}
|
||||
|
||||
if (
|
||||
prevProps.inputText !== this.props.inputText &&
|
||||
this.props.inputText !== toString(this.props.text)
|
||||
|
|
@ -732,7 +746,12 @@ class InputWidget extends BaseInputWidget<InputWidgetProps, WidgetState> {
|
|||
}
|
||||
};
|
||||
|
||||
onValueChange = (value: string) => {
|
||||
componentWillUnmount() {
|
||||
this.debouncedOnValueChange.cancel();
|
||||
}
|
||||
|
||||
// debouncing the input change to avoid multiple Execute calls in reactive flow
|
||||
debouncedOnValueChange = debounce((value: string) => {
|
||||
/*
|
||||
* Ideally text property should be derived property. But widgets
|
||||
* with derived properties won't work as expected inside a List
|
||||
|
|
@ -755,6 +774,11 @@ class InputWidget extends BaseInputWidget<InputWidgetProps, WidgetState> {
|
|||
if (!this.props.isDirty) {
|
||||
this.props.updateWidgetMetaProperty("isDirty", true);
|
||||
}
|
||||
}, DEBOUNCE_WAIT_TIME_ON_INPUT_CHANGE);
|
||||
|
||||
onValueChange = (value: string) => {
|
||||
this.setState({ inputValue: value });
|
||||
this.debouncedOnValueChange(value);
|
||||
};
|
||||
|
||||
static getSetterConfig(): SetterConfig {
|
||||
|
|
@ -790,7 +814,7 @@ class InputWidget extends BaseInputWidget<InputWidgetProps, WidgetState> {
|
|||
};
|
||||
|
||||
getWidgetView() {
|
||||
const value = this.props.inputText ?? "";
|
||||
const value = this.state.inputValue ?? "";
|
||||
let isInvalid = false;
|
||||
|
||||
if (this.props.isDirty) {
|
||||
|
|
|
|||
|
|
@ -12,7 +12,7 @@ import {
|
|||
ISDCodeDropdownOptions,
|
||||
} from "../component/ISDCodeDropdown";
|
||||
import { AutocompleteDataType } from "utils/autocomplete/AutocompleteDataType";
|
||||
import _ from "lodash";
|
||||
import _, { debounce } from "lodash";
|
||||
import BaseInputWidget from "widgets/BaseInputWidget";
|
||||
import derivedProperties from "./parsedDerivedProperties";
|
||||
import type { BaseInputWidgetProps } from "widgets/BaseInputWidget/widget";
|
||||
|
|
@ -37,7 +37,10 @@ import { DynamicHeight } from "utils/WidgetFeatures";
|
|||
import { getDefaultISDCode } from "../component/ISDCodeDropdown";
|
||||
import IconSVG from "../icon.svg";
|
||||
import ThumbnailSVG from "../thumbnail.svg";
|
||||
import { WIDGET_TAGS } from "constants/WidgetConstants";
|
||||
import {
|
||||
DEBOUNCE_WAIT_TIME_ON_INPUT_CHANGE,
|
||||
WIDGET_TAGS,
|
||||
} from "constants/WidgetConstants";
|
||||
import { appsmithTelemetry } from "instrumentation";
|
||||
|
||||
export function defaultValueValidation(
|
||||
|
|
@ -84,10 +87,21 @@ export function defaultValueValidation(
|
|||
};
|
||||
}
|
||||
|
||||
interface PhoneWidgetState extends WidgetState {
|
||||
inputValue: string;
|
||||
}
|
||||
|
||||
class PhoneInputWidget extends BaseInputWidget<
|
||||
PhoneInputWidgetProps,
|
||||
WidgetState
|
||||
PhoneWidgetState
|
||||
> {
|
||||
constructor(props: PhoneInputWidgetProps) {
|
||||
super(props);
|
||||
this.state = {
|
||||
inputValue: props.text ?? "",
|
||||
};
|
||||
}
|
||||
|
||||
static type = "PHONE_INPUT_WIDGET";
|
||||
|
||||
static getConfig() {
|
||||
|
|
@ -356,6 +370,12 @@ class PhoneInputWidget extends BaseInputWidget<
|
|||
}
|
||||
|
||||
componentDidUpdate(prevProps: PhoneInputWidgetProps) {
|
||||
if (prevProps.text !== this.props.text) {
|
||||
this.setState({ inputValue: this.props.text ?? "" });
|
||||
// Cancel any pending debounced calls when value is updated externally
|
||||
this.debouncedOnValueChange.cancel();
|
||||
}
|
||||
|
||||
if (prevProps.dialCode !== this.props.dialCode) {
|
||||
this.onISDCodeChange(this.props.dialCode);
|
||||
}
|
||||
|
|
@ -390,6 +410,10 @@ class PhoneInputWidget extends BaseInputWidget<
|
|||
}
|
||||
}
|
||||
|
||||
componentWillUnmount() {
|
||||
this.debouncedOnValueChange.cancel();
|
||||
}
|
||||
|
||||
onISDCodeChange = (dialCode?: string) => {
|
||||
const countryCode = getCountryCode(dialCode);
|
||||
|
||||
|
|
@ -403,21 +427,13 @@ class PhoneInputWidget extends BaseInputWidget<
|
|||
}
|
||||
};
|
||||
|
||||
onValueChange = (value: string) => {
|
||||
let formattedValue;
|
||||
|
||||
// Don't format, as value is typed, when user is deleting
|
||||
if (value && value.length > this.props.text?.length) {
|
||||
formattedValue = this.getFormattedPhoneNumber(value);
|
||||
} else {
|
||||
formattedValue = value;
|
||||
}
|
||||
|
||||
// debouncing the input change to avoid multiple Execute calls in reactive flow
|
||||
debouncedOnValueChange = debounce((value: string) => {
|
||||
this.props.updateWidgetMetaProperty(
|
||||
"value",
|
||||
parseIncompletePhoneNumber(formattedValue),
|
||||
parseIncompletePhoneNumber(value),
|
||||
);
|
||||
this.props.updateWidgetMetaProperty("text", formattedValue, {
|
||||
this.props.updateWidgetMetaProperty("text", value, {
|
||||
triggerPropertyName: "onTextChanged",
|
||||
dynamicString: this.props.onTextChanged,
|
||||
event: {
|
||||
|
|
@ -428,6 +444,20 @@ class PhoneInputWidget extends BaseInputWidget<
|
|||
if (!this.props.isDirty) {
|
||||
this.props.updateWidgetMetaProperty("isDirty", true);
|
||||
}
|
||||
}, DEBOUNCE_WAIT_TIME_ON_INPUT_CHANGE);
|
||||
|
||||
onValueChange = (value: string) => {
|
||||
let formattedValue;
|
||||
|
||||
// Don't format, as value is typed, when user is deleting
|
||||
if (value && value.length > this.props.text?.length) {
|
||||
formattedValue = this.getFormattedPhoneNumber(value);
|
||||
} else {
|
||||
formattedValue = value;
|
||||
}
|
||||
|
||||
this.setState({ inputValue: formattedValue });
|
||||
this.debouncedOnValueChange(formattedValue);
|
||||
};
|
||||
|
||||
handleFocusChange = (focusState: boolean) => {
|
||||
|
|
@ -487,7 +517,7 @@ class PhoneInputWidget extends BaseInputWidget<
|
|||
}
|
||||
|
||||
getWidgetView() {
|
||||
const value = this.props.text ?? "";
|
||||
const value = this.state.inputValue;
|
||||
const isInvalid =
|
||||
"isValid" in this.props && !this.props.isValid && !!this.props.isDirty;
|
||||
const countryCode = this.props.countryCode;
|
||||
|
|
|
|||
|
|
@ -34,7 +34,11 @@ import type {
|
|||
SnipingModeProperty,
|
||||
PropertyUpdates,
|
||||
} from "WidgetProvider/constants";
|
||||
import { WIDGET_TAGS } from "constants/WidgetConstants";
|
||||
import {
|
||||
DEBOUNCE_WAIT_TIME_ON_INPUT_CHANGE,
|
||||
WIDGET_TAGS,
|
||||
} from "constants/WidgetConstants";
|
||||
import { debounce } from "lodash";
|
||||
|
||||
export enum RTEFormats {
|
||||
MARKDOWN = "markdown",
|
||||
|
|
@ -48,10 +52,21 @@ const RichTextEditorComponent = lazy(async () =>
|
|||
|
||||
const converter = new showdown.Converter();
|
||||
|
||||
interface RichTextEditorWidgetState extends WidgetState {
|
||||
inputValue: string;
|
||||
}
|
||||
|
||||
class RichTextEditorWidget extends BaseWidget<
|
||||
RichTextEditorWidgetProps,
|
||||
WidgetState
|
||||
RichTextEditorWidgetState
|
||||
> {
|
||||
constructor(props: RichTextEditorWidgetProps) {
|
||||
super(props);
|
||||
this.state = {
|
||||
inputValue: props.text ?? "",
|
||||
};
|
||||
}
|
||||
|
||||
static type = "RICH_TEXT_EDITOR_WIDGET";
|
||||
|
||||
static getConfig() {
|
||||
|
|
@ -491,6 +506,12 @@ class RichTextEditorWidget extends BaseWidget<
|
|||
}
|
||||
|
||||
componentDidUpdate(prevProps: RichTextEditorWidgetProps): void {
|
||||
if (prevProps.text !== this.props.text) {
|
||||
this.setState({ inputValue: this.props.text ?? "" });
|
||||
// Cancel any pending debounced calls when value is updated externally
|
||||
this.debouncedOnValueChange.cancel();
|
||||
}
|
||||
|
||||
if (this.props.defaultText !== prevProps.defaultText) {
|
||||
if (this.props.isDirty) {
|
||||
this.props.updateWidgetMetaProperty("isDirty", false);
|
||||
|
|
@ -498,7 +519,12 @@ class RichTextEditorWidget extends BaseWidget<
|
|||
}
|
||||
}
|
||||
|
||||
onValueChange = (text: string) => {
|
||||
componentWillUnmount() {
|
||||
this.debouncedOnValueChange.cancel();
|
||||
}
|
||||
|
||||
// debouncing the input change to avoid multiple Execute calls in reactive flow
|
||||
debouncedOnValueChange = debounce((text: string) => {
|
||||
if (!this.props.isDirty) {
|
||||
this.props.updateWidgetMetaProperty("isDirty", true);
|
||||
}
|
||||
|
|
@ -510,6 +536,11 @@ class RichTextEditorWidget extends BaseWidget<
|
|||
type: EventType.ON_TEXT_CHANGE,
|
||||
},
|
||||
});
|
||||
}, DEBOUNCE_WAIT_TIME_ON_INPUT_CHANGE);
|
||||
|
||||
onValueChange = (text: string) => {
|
||||
this.setState({ inputValue: text });
|
||||
this.debouncedOnValueChange(text);
|
||||
};
|
||||
|
||||
static getSetterConfig(): SetterConfig {
|
||||
|
|
@ -532,7 +563,7 @@ class RichTextEditorWidget extends BaseWidget<
|
|||
}
|
||||
|
||||
getWidgetView() {
|
||||
let value = this.props.text ?? "";
|
||||
let value = this.state.inputValue;
|
||||
|
||||
if (this.props.inputType === RTEFormats.MARKDOWN) {
|
||||
value = converter.makeHtml(value);
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
import _ from "lodash";
|
||||
import _, { debounce } from "lodash";
|
||||
import React from "react";
|
||||
import log from "loglevel";
|
||||
import type { WidgetState } from "widgets/BaseWidget";
|
||||
|
|
@ -30,11 +30,20 @@ import { WDSBaseInputWidget } from "widgets/wds/WDSBaseInputWidget";
|
|||
import { getCountryCodeFromCurrencyCode, validateInput } from "./helpers";
|
||||
import type { KeyDownEvent } from "widgets/wds/WDSBaseInputWidget/component/types";
|
||||
import { appsmithTelemetry } from "instrumentation";
|
||||
import { DEBOUNCE_WAIT_TIME_ON_INPUT_CHANGE } from "constants/WidgetConstants";
|
||||
|
||||
interface WDSCurrencyInputWidgetState extends WidgetState {
|
||||
inputValue: string;
|
||||
}
|
||||
class WDSCurrencyInputWidget extends WDSBaseInputWidget<
|
||||
CurrencyInputWidgetProps,
|
||||
WidgetState
|
||||
WDSCurrencyInputWidgetState
|
||||
> {
|
||||
constructor(props: CurrencyInputWidgetProps) {
|
||||
super(props);
|
||||
this.state = { inputValue: props.rawText ?? "" };
|
||||
}
|
||||
|
||||
static type = "WDS_CURRENCY_INPUT_WIDGET";
|
||||
|
||||
static getConfig() {
|
||||
|
|
@ -152,6 +161,12 @@ class WDSCurrencyInputWidget extends WDSBaseInputWidget<
|
|||
}
|
||||
|
||||
componentDidUpdate(prevProps: CurrencyInputWidgetProps) {
|
||||
if (prevProps.rawText !== this.props.rawText) {
|
||||
this.setState({ inputValue: this.props.rawText ?? "" });
|
||||
// Cancel any pending debounced calls when value is updated externally
|
||||
this.debouncedOnValueChange.cancel();
|
||||
}
|
||||
|
||||
if (
|
||||
prevProps.text !== this.props.text &&
|
||||
!this.props.isFocused &&
|
||||
|
|
@ -176,6 +191,27 @@ class WDSCurrencyInputWidget extends WDSBaseInputWidget<
|
|||
}
|
||||
}
|
||||
|
||||
componentWillUnmount(): void {
|
||||
this.debouncedOnValueChange.cancel();
|
||||
}
|
||||
|
||||
// debouncing the input change to avoid multiple Execute calls in reactive flow
|
||||
debouncedOnValueChange = debounce((value: string, formattedValue: string) => {
|
||||
this.props.updateWidgetMetaProperty("text", String(formattedValue));
|
||||
|
||||
this.props.updateWidgetMetaProperty("rawText", value, {
|
||||
triggerPropertyName: "onTextChanged",
|
||||
dynamicString: this.props.onTextChanged,
|
||||
event: {
|
||||
type: EventType.ON_TEXT_CHANGE,
|
||||
},
|
||||
});
|
||||
|
||||
if (!this.props.isDirty) {
|
||||
this.props.updateWidgetMetaProperty("isDirty", true);
|
||||
}
|
||||
}, DEBOUNCE_WAIT_TIME_ON_INPUT_CHANGE);
|
||||
|
||||
onValueChange = (value: string) => {
|
||||
let formattedValue = "";
|
||||
const decimalSeperator = getLocaleDecimalSeperator();
|
||||
|
|
@ -194,19 +230,8 @@ class WDSCurrencyInputWidget extends WDSBaseInputWidget<
|
|||
});
|
||||
}
|
||||
|
||||
this.props.updateWidgetMetaProperty("text", String(formattedValue));
|
||||
|
||||
this.props.updateWidgetMetaProperty("rawText", value, {
|
||||
triggerPropertyName: "onTextChanged",
|
||||
dynamicString: this.props.onTextChanged,
|
||||
event: {
|
||||
type: EventType.ON_TEXT_CHANGE,
|
||||
},
|
||||
});
|
||||
|
||||
if (!this.props.isDirty) {
|
||||
this.props.updateWidgetMetaProperty("isDirty", true);
|
||||
}
|
||||
this.setState({ inputValue: formattedValue });
|
||||
this.debouncedOnValueChange(value, formattedValue);
|
||||
};
|
||||
|
||||
onFocusChange = (isFocused?: boolean) => {
|
||||
|
|
@ -323,7 +348,7 @@ class WDSCurrencyInputWidget extends WDSBaseInputWidget<
|
|||
}
|
||||
|
||||
getWidgetView() {
|
||||
const value = this.props.rawText ?? "";
|
||||
const value = this.state.inputValue;
|
||||
const validation = validateInput(this.props);
|
||||
|
||||
return (
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
import React from "react";
|
||||
import { isNumber, merge, toString } from "lodash";
|
||||
import { debounce, isNumber, merge, toString } from "lodash";
|
||||
import * as config from "../config";
|
||||
import InputComponent from "../component";
|
||||
import type { InputWidgetProps } from "./types";
|
||||
|
|
@ -14,8 +14,21 @@ import { EventType } from "constants/AppsmithActionConstants/ActionConstants";
|
|||
import type { KeyDownEvent } from "widgets/wds/WDSBaseInputWidget/component/types";
|
||||
import type { WidgetBaseConfiguration } from "WidgetProvider/constants";
|
||||
import { INPUT_TYPES } from "widgets/wds/WDSBaseInputWidget/constants";
|
||||
import { DEBOUNCE_WAIT_TIME_ON_INPUT_CHANGE } from "constants/WidgetConstants";
|
||||
|
||||
interface WDSInputWidgetState extends WidgetState {
|
||||
inputValue: string;
|
||||
}
|
||||
|
||||
class WDSInputWidget extends WDSBaseInputWidget<
|
||||
InputWidgetProps,
|
||||
WDSInputWidgetState
|
||||
> {
|
||||
constructor(props: InputWidgetProps) {
|
||||
super(props);
|
||||
this.state = { inputValue: props.rawText ?? "" };
|
||||
}
|
||||
|
||||
class WDSInputWidget extends WDSBaseInputWidget<InputWidgetProps, WidgetState> {
|
||||
static type = "WDS_INPUT_WIDGET";
|
||||
|
||||
static getConfig(): WidgetBaseConfiguration {
|
||||
|
|
@ -155,6 +168,12 @@ class WDSInputWidget extends WDSBaseInputWidget<InputWidgetProps, WidgetState> {
|
|||
componentDidUpdate = (prevProps: InputWidgetProps) => {
|
||||
const { commitBatchMetaUpdates, pushBatchMetaUpdates } = this.props;
|
||||
|
||||
if (prevProps.rawText !== this.props.rawText) {
|
||||
this.setState({ inputValue: this.props.rawText ?? "" });
|
||||
// Cancel any pending debounced calls when value is updated externally
|
||||
this.debouncedOnValueChange.cancel();
|
||||
}
|
||||
|
||||
if (
|
||||
prevProps.rawText !== this.props.rawText &&
|
||||
this.props.rawText !== toString(this.props.text)
|
||||
|
|
@ -183,7 +202,12 @@ class WDSInputWidget extends WDSBaseInputWidget<InputWidgetProps, WidgetState> {
|
|||
commitBatchMetaUpdates();
|
||||
};
|
||||
|
||||
onValueChange = (value: string) => {
|
||||
componentWillUnmount(): void {
|
||||
this.debouncedOnValueChange.cancel();
|
||||
}
|
||||
|
||||
// debouncing the input change to avoid multiple Execute calls in reactive flow
|
||||
debouncedOnValueChange = debounce((value: string) => {
|
||||
const { commitBatchMetaUpdates, pushBatchMetaUpdates } = this.props;
|
||||
|
||||
// Ideally text property should be derived property. But widgets with
|
||||
|
|
@ -205,6 +229,11 @@ class WDSInputWidget extends WDSBaseInputWidget<InputWidgetProps, WidgetState> {
|
|||
}
|
||||
|
||||
commitBatchMetaUpdates();
|
||||
}, DEBOUNCE_WAIT_TIME_ON_INPUT_CHANGE);
|
||||
|
||||
onValueChange = (value: string) => {
|
||||
this.setState({ inputValue: value });
|
||||
this.debouncedOnValueChange(value);
|
||||
};
|
||||
|
||||
resetWidgetText = () => {
|
||||
|
|
@ -226,9 +255,9 @@ class WDSInputWidget extends WDSBaseInputWidget<InputWidgetProps, WidgetState> {
|
|||
};
|
||||
|
||||
getWidgetView() {
|
||||
const { inputType, rawText } = this.props;
|
||||
const { inputType } = this.props;
|
||||
|
||||
const value = rawText ?? "";
|
||||
const value = this.state.inputValue;
|
||||
const { errorMessage, validationStatus } = validateInput(this.props);
|
||||
|
||||
return (
|
||||
|
|
|
|||
|
|
@ -21,11 +21,22 @@ import { PhoneInputComponent } from "../component";
|
|||
import type { PhoneInputWidgetProps } from "./types";
|
||||
import { getCountryCode, validateInput } from "./helpers";
|
||||
import { appsmithTelemetry } from "instrumentation";
|
||||
import { debounce } from "lodash";
|
||||
import { DEBOUNCE_WAIT_TIME_ON_INPUT_CHANGE } from "constants/WidgetConstants";
|
||||
|
||||
interface WDSPhoneInputWidgetState extends WidgetState {
|
||||
inputValue: string;
|
||||
}
|
||||
|
||||
class WDSPhoneInputWidget extends WDSBaseInputWidget<
|
||||
PhoneInputWidgetProps,
|
||||
WidgetState
|
||||
WDSPhoneInputWidgetState
|
||||
> {
|
||||
constructor(props: PhoneInputWidgetProps) {
|
||||
super(props);
|
||||
this.state = { inputValue: props.text ?? "" };
|
||||
}
|
||||
|
||||
static type = "WDS_PHONE_INPUT_WIDGET";
|
||||
|
||||
static getConfig() {
|
||||
|
|
@ -171,6 +182,12 @@ class WDSPhoneInputWidget extends WDSBaseInputWidget<
|
|||
}
|
||||
|
||||
componentDidUpdate(prevProps: PhoneInputWidgetProps) {
|
||||
if (prevProps.text !== this.props.text) {
|
||||
this.setState({ inputValue: this.props.text ?? "" });
|
||||
// Cancel any pending debounced calls when value is updated externally
|
||||
this.debouncedOnValueChange.cancel();
|
||||
}
|
||||
|
||||
if (prevProps.dialCode !== this.props.dialCode) {
|
||||
this.onISDCodeChange(this.props.dialCode);
|
||||
}
|
||||
|
|
@ -205,6 +222,10 @@ class WDSPhoneInputWidget extends WDSBaseInputWidget<
|
|||
}
|
||||
}
|
||||
|
||||
componentWillUnmount(): void {
|
||||
this.debouncedOnValueChange.cancel();
|
||||
}
|
||||
|
||||
onISDCodeChange = (dialCode?: string) => {
|
||||
const countryCode = getCountryCode(dialCode);
|
||||
|
||||
|
|
@ -218,21 +239,13 @@ class WDSPhoneInputWidget extends WDSBaseInputWidget<
|
|||
}
|
||||
};
|
||||
|
||||
onValueChange = (value: string) => {
|
||||
let formattedValue;
|
||||
|
||||
// Don't format, as value is typed, when user is deleting
|
||||
if (value && value.length > this.props.text?.length) {
|
||||
formattedValue = this.getFormattedPhoneNumber(value);
|
||||
} else {
|
||||
formattedValue = value;
|
||||
}
|
||||
|
||||
// debouncing the input change to avoid multiple Execute calls in reactive flow
|
||||
debouncedOnValueChange = debounce((value: string) => {
|
||||
this.props.updateWidgetMetaProperty(
|
||||
"rawText",
|
||||
parseIncompletePhoneNumber(formattedValue),
|
||||
parseIncompletePhoneNumber(value),
|
||||
);
|
||||
this.props.updateWidgetMetaProperty("text", formattedValue, {
|
||||
this.props.updateWidgetMetaProperty("text", value, {
|
||||
triggerPropertyName: "onTextChanged",
|
||||
dynamicString: this.props.onTextChanged,
|
||||
event: {
|
||||
|
|
@ -243,6 +256,20 @@ class WDSPhoneInputWidget extends WDSBaseInputWidget<
|
|||
if (!this.props.isDirty) {
|
||||
this.props.updateWidgetMetaProperty("isDirty", true);
|
||||
}
|
||||
}, DEBOUNCE_WAIT_TIME_ON_INPUT_CHANGE);
|
||||
|
||||
onValueChange = (value: string) => {
|
||||
let formattedValue;
|
||||
|
||||
// Don't format, as value is typed, when user is deleting
|
||||
if (value && value.length > this.props.text?.length) {
|
||||
formattedValue = this.getFormattedPhoneNumber(value);
|
||||
} else {
|
||||
formattedValue = value;
|
||||
}
|
||||
|
||||
this.setState({ inputValue: formattedValue });
|
||||
this.debouncedOnValueChange(formattedValue);
|
||||
};
|
||||
|
||||
onFocusChange = (focusState: boolean) => {
|
||||
|
|
@ -300,7 +327,7 @@ class WDSPhoneInputWidget extends WDSBaseInputWidget<
|
|||
};
|
||||
|
||||
getWidgetView() {
|
||||
const rawText = this.props.text ?? "";
|
||||
const rawText = this.state.inputValue;
|
||||
|
||||
const validation = validateInput(this.props);
|
||||
|
||||
|
|
|
|||
Loading…
Reference in New Issue
Block a user