chore: fix form widgets bugs (#38492)

/ok-to-test tags="@tag.Anvil"

Fixes #38200
Fixes #38201
Fixes #38409
Fixes #38410

<!-- This is an auto-generated comment: release notes by coderabbit.ai
-->
## Summary by CodeRabbit

Based on the comprehensive summary, here are the updated release notes:

- **New Features**
- Enhanced calendar functionality with month and year dropdown
selection.
    - Improved input and select component styling.
    - Added text wrapping and line clamping for field labels.

- **Bug Fixes**
- Refined input validation and error handling across multiple widgets.
    - Updated text property handling for various input widgets.

- **Documentation**
    - Updated autocomplete configuration for input widgets.

- **Chores**
- Temporarily disabled several Cypress test suites for Anvil widgets.
    - Standardized text property naming across input-related components.

- **Style**
- Improved CSS styling for input groups, dropdowns, and calendar
components.
    - Enhanced text rendering and whitespace handling.

These release notes capture the key changes across the design system and
widget components, focusing on user-facing improvements and internal
refinements.
<!-- end of auto-generated comment: release notes by coderabbit.ai -->

<!-- This is an auto-generated comment: Cypress test results  -->
> [!TIP]
> 🟢 🟢 🟢 All cypress tests have passed! 🎉 🎉 🎉
> Workflow run:
<https://github.com/appsmithorg/appsmith/actions/runs/12650457693>
> Commit: c41781c32b9fd186b718338556af9a53221eaca7
> <a
href="https://internal.appsmith.com/app/cypress-dashboard/rundetails-65890b3c81d7400d08fa9ee5?branch=master&workflowId=12650457693&attempt=1"
target="_blank">Cypress dashboard</a>.
> Tags: `@tag.Anvil`
> Spec:
> <hr>Tue, 07 Jan 2025 11:41:09 UTC
<!-- end of auto-generated comment: Cypress test results  -->
This commit is contained in:
Pawan Kumar 2025-01-07 17:34:15 +05:30 committed by GitHub
parent f6d7ce626f
commit a9a0d714c0
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
35 changed files with 249 additions and 85 deletions

View File

@ -5,7 +5,7 @@ import {
} from "../../../../../support/Objects/ObjectsCore"; } from "../../../../../support/Objects/ObjectsCore";
// TODO: Enable when issue(github.com/appsmithorg/appsmith/issues/36419) is solved. // TODO: Enable when issue(github.com/appsmithorg/appsmith/issues/36419) is solved.
describe( describe.skip(
`${ANVIL_EDITOR_TEST}: Anvil tests for Button Widget`, `${ANVIL_EDITOR_TEST}: Anvil tests for Button Widget`,
{ tags: ["@tag.Anvil", "@tag.Visual"] }, { tags: ["@tag.Anvil", "@tag.Visual"] },
() => { () => {

View File

@ -5,7 +5,7 @@ import {
} from "../../../../../support/Objects/ObjectsCore"; } from "../../../../../support/Objects/ObjectsCore";
// TODO: Enable when issue(github.com/appsmithorg/appsmith/issues/36419) is solved. // TODO: Enable when issue(github.com/appsmithorg/appsmith/issues/36419) is solved.
describe( describe.skip(
`${ANVIL_EDITOR_TEST}: Anvil tests for Checkbox Group Widget`, `${ANVIL_EDITOR_TEST}: Anvil tests for Checkbox Group Widget`,
{ tags: ["@tag.Anvil", "@tag.Visual"] }, { tags: ["@tag.Anvil", "@tag.Visual"] },
() => { () => {

View File

@ -5,7 +5,7 @@ import {
} from "../../../../../support/Objects/ObjectsCore"; } from "../../../../../support/Objects/ObjectsCore";
// TODO: Enable when issue(github.com/appsmithorg/appsmith/issues/36419) is solved. // TODO: Enable when issue(github.com/appsmithorg/appsmith/issues/36419) is solved.
describe( describe.skip(
`${ANVIL_EDITOR_TEST}: Anvil tests for Checkbox Widget`, `${ANVIL_EDITOR_TEST}: Anvil tests for Checkbox Widget`,
{ tags: ["@tag.Anvil", "@tag.Visual"] }, { tags: ["@tag.Anvil", "@tag.Visual"] },
() => { () => {

View File

@ -5,7 +5,7 @@ import {
} from "../../../../../support/Objects/ObjectsCore"; } from "../../../../../support/Objects/ObjectsCore";
// TODO: Enable when issue(github.com/appsmithorg/appsmith/issues/36419) is solved. // TODO: Enable when issue(github.com/appsmithorg/appsmith/issues/36419) is solved.
describe( describe.skip(
`${ANVIL_EDITOR_TEST}: Anvil tests for Heading Widget`, `${ANVIL_EDITOR_TEST}: Anvil tests for Heading Widget`,
{ tags: ["@tag.Anvil", "@tag.Visual"] }, { tags: ["@tag.Anvil", "@tag.Visual"] },
() => { () => {

View File

@ -5,7 +5,7 @@ import {
} from "../../../../../support/Objects/ObjectsCore"; } from "../../../../../support/Objects/ObjectsCore";
// TODO: Enable when issue(github.com/appsmithorg/appsmith/issues/36419) is solved. // TODO: Enable when issue(github.com/appsmithorg/appsmith/issues/36419) is solved.
describe( describe.skip(
`${ANVIL_EDITOR_TEST}: Anvil tests for Icon Button Widget`, `${ANVIL_EDITOR_TEST}: Anvil tests for Icon Button Widget`,
{ tags: ["@tag.Anvil", "@tag.Visual"] }, { tags: ["@tag.Anvil", "@tag.Visual"] },
() => { () => {

View File

@ -5,7 +5,7 @@ import {
} from "../../../../../support/Objects/ObjectsCore"; } from "../../../../../support/Objects/ObjectsCore";
// TODO: Enable when issue(github.com/appsmithorg/appsmith/issues/36419) is solved. // TODO: Enable when issue(github.com/appsmithorg/appsmith/issues/36419) is solved.
describe( describe.skip(
`${ANVIL_EDITOR_TEST}: Anvil tests for Inline Button Widget`, `${ANVIL_EDITOR_TEST}: Anvil tests for Inline Button Widget`,
{ tags: ["@tag.Anvil", "@tag.Visual"] }, { tags: ["@tag.Anvil", "@tag.Visual"] },
() => { () => {

View File

@ -5,7 +5,7 @@ import {
} from "../../../../../support/Objects/ObjectsCore"; } from "../../../../../support/Objects/ObjectsCore";
// TODO: Enable when issue(github.com/appsmithorg/appsmith/issues/36419) is solved. // TODO: Enable when issue(github.com/appsmithorg/appsmith/issues/36419) is solved.
describe( describe.skip(
`${ANVIL_EDITOR_TEST}: Anvil tests for Paragraph Widget`, `${ANVIL_EDITOR_TEST}: Anvil tests for Paragraph Widget`,
{ tags: ["@tag.Anvil", "@tag.Visual"] }, { tags: ["@tag.Anvil", "@tag.Visual"] },
() => { () => {

View File

@ -5,7 +5,7 @@ import {
} from "../../../../../support/Objects/ObjectsCore"; } from "../../../../../support/Objects/ObjectsCore";
// TODO: Enable when issue(github.com/appsmithorg/appsmith/issues/36419) is solved. // TODO: Enable when issue(github.com/appsmithorg/appsmith/issues/36419) is solved.
describe( describe.skip(
`${ANVIL_EDITOR_TEST}: Anvil tests for Radio Group Widget`, `${ANVIL_EDITOR_TEST}: Anvil tests for Radio Group Widget`,
{ tags: ["@tag.Anvil", "@tag.Visual"] }, { tags: ["@tag.Anvil", "@tag.Visual"] },
() => { () => {

View File

@ -5,7 +5,7 @@ import {
} from "../../../../../support/Objects/ObjectsCore"; } from "../../../../../support/Objects/ObjectsCore";
// TODO: Enable when issue(github.com/appsmithorg/appsmith/issues/36419) is solved. // TODO: Enable when issue(github.com/appsmithorg/appsmith/issues/36419) is solved.
describe( describe.skip(
`${ANVIL_EDITOR_TEST}: Anvil tests for Stats Widget`, `${ANVIL_EDITOR_TEST}: Anvil tests for Stats Widget`,
{ tags: ["@tag.Anvil", "@tag.Visual"] }, { tags: ["@tag.Anvil", "@tag.Visual"] },
() => { () => {

View File

@ -5,7 +5,7 @@ import {
} from "../../../../../support/Objects/ObjectsCore"; } from "../../../../../support/Objects/ObjectsCore";
// TODO: Enable when issue(github.com/appsmithorg/appsmith/issues/36419) is solved. // TODO: Enable when issue(github.com/appsmithorg/appsmith/issues/36419) is solved.
describe( describe.skip(
`${ANVIL_EDITOR_TEST}: Anvil tests for Switch Group Widget`, `${ANVIL_EDITOR_TEST}: Anvil tests for Switch Group Widget`,
{ tags: ["@tag.Anvil", "@tag.Visual"] }, { tags: ["@tag.Anvil", "@tag.Visual"] },
() => { () => {

View File

@ -5,7 +5,7 @@ import {
} from "../../../../../support/Objects/ObjectsCore"; } from "../../../../../support/Objects/ObjectsCore";
// TODO: Enable when issue(github.com/appsmithorg/appsmith/issues/36419) is solved. // TODO: Enable when issue(github.com/appsmithorg/appsmith/issues/36419) is solved.
describe( describe.skip(
`${ANVIL_EDITOR_TEST}: Anvil tests for Switch Widget`, `${ANVIL_EDITOR_TEST}: Anvil tests for Switch Widget`,
{ tags: ["@tag.Anvil", "@tag.Visual"] }, { tags: ["@tag.Anvil", "@tag.Visual"] },
() => { () => {

View File

@ -5,7 +5,7 @@ import {
} from "../../../../../support/Objects/ObjectsCore"; } from "../../../../../support/Objects/ObjectsCore";
// TODO: Enable when issue(github.com/appsmithorg/appsmith/issues/36419) is solved. // TODO: Enable when issue(github.com/appsmithorg/appsmith/issues/36419) is solved.
describe( describe.skip(
`${ANVIL_EDITOR_TEST}: Anvil tests for Toolbar Button Widget`, `${ANVIL_EDITOR_TEST}: Anvil tests for Toolbar Button Widget`,
{ tags: ["@tag.Anvil", "@tag.Visual"] }, { tags: ["@tag.Anvil", "@tag.Visual"] },
() => { () => {

View File

@ -1,6 +1,7 @@
# To run only limited tests - give the spec names in below format: # To run only limited tests - give the spec names in below format:
cypress/e2e/Regression/ClientSide/VisualTests/JSEditorIndent_spec.js #cypress/e2e/Regression/ClientSide/VisualTests/JSEditorIndent_spec.js
# For running all specs - uncomment below: # For running all specs - uncomment below:
#cypress/e2e/**/**/* #cypress/e2e/**/**/*
cypress/e2e/Regression/ClientSide/Anvil/Widgets/*
#ci-test-limit uses this file to run minimum of specs. Do not run entire suite with this command. #ci-test-limit uses this file to run minimum of specs. Do not run entire suite with this command.

View File

@ -173,8 +173,8 @@ export class AnvilSnapshot {
public triggerInputInvalidState = () => { public triggerInputInvalidState = () => {
this.enterPreviewMode(); this.enterPreviewMode();
cy.get("input[aria-required=true]").first().type("123"); cy.get("input[required]").first().type("123");
cy.get("input[aria-required=true]").first().clear(); cy.get("input[required]").first().clear();
this.exitPreviewMode(); this.exitPreviewMode();
this.agHelper.GetNClick(this.locators.propertyPaneSidebar); this.agHelper.GetNClick(this.locators.propertyPaneSidebar);
}; };

View File

@ -1,18 +1,27 @@
import { Text, type TextProps } from "@appsmith/wds"; import { type TextProps } from "@appsmith/wds";
import React, { forwardRef, type ForwardedRef } from "react"; import {
import { HeadingContext, useContextProps } from "react-aria-components"; CalendarStateContext,
HeadingContext,
useContextProps,
} from "react-aria-components";
import React, { forwardRef, useContext, type ForwardedRef } from "react";
import styles from "./styles.module.css";
import { CalendarMonthDropdown } from "./CalendarMonthDropdown";
import { CalendarYearDropdown } from "./CalendarYearDropdown";
function CalendarHeading( function CalendarHeading(
props: TextProps, props: TextProps,
ref: ForwardedRef<HTMLHeadingElement>, ref: ForwardedRef<HTMLHeadingElement>,
) { ) {
[props, ref] = useContextProps(props, ref, HeadingContext); [props, ref] = useContextProps(props, ref, HeadingContext);
const { children, ...domProps } = props; const state = useContext(CalendarStateContext);
return ( return (
<Text {...domProps} color="neutral" ref={ref}> <div className={styles.monthYearDropdown}>
{children} <CalendarMonthDropdown state={state} />
</Text> <CalendarYearDropdown state={state} />
</div>
); );
} }

View File

@ -0,0 +1,40 @@
import React from "react";
import type { Key } from "react";
import { useDateFormatter } from "@react-aria/i18n";
import { ListBoxItem, Select } from "@appsmith/wds";
import type { CalendarState } from "@react-stately/calendar";
import styles from "./styles.module.css";
import { useValidMonths } from "../utils/calendar";
export function CalendarMonthDropdown({ state }: { state: CalendarState }) {
const formatter = useDateFormatter({
month: "long",
timeZone: state.timeZone,
});
const months = useValidMonths(state, formatter);
const onChange = (value: Key | null) => {
const date = state.focusedDate.set({ month: Number(value) });
state.setFocusedDate(date);
};
return (
<Select
aria-label="Month"
className={styles.monthDropdown}
defaultSelectedKey={state.focusedDate.month}
onSelectionChange={onChange}
placeholder="Select Month"
size="small"
>
{months.map((month, i) => (
<ListBoxItem id={i} key={i} textValue={month}>
{month}
</ListBoxItem>
))}
</Select>
);
}

View File

@ -0,0 +1,33 @@
import React from "react";
import type { Key } from "react";
import { ListBoxItem, Select } from "@appsmith/wds";
import type { CalendarState } from "@react-stately/calendar";
import { useYearOptions } from "../utils/calendar";
export function CalendarYearDropdown({ state }: { state: CalendarState }) {
const years = useYearOptions(state);
const onChange = (value: Key | null) => {
const index = Number(value);
const date = years[index].value;
state.setFocusedDate(date);
};
return (
<Select
aria-label="Year"
onSelectionChange={onChange}
placeholder="Select Year"
selectedKey={20}
size="small"
>
{years.map((year, i) => (
<ListBoxItem id={i} key={i} textValue={year.formatted}>
{year.formatted}
</ListBoxItem>
))}
</Select>
);
}

View File

@ -68,3 +68,12 @@
0 0 0 calc(var(--box-shadow-offset) + var(--border-width-2)) 0 0 0 calc(var(--box-shadow-offset) + var(--border-width-2))
var(--color-bd-focus); var(--color-bd-focus);
} }
.monthYearDropdown {
display: flex;
gap: var(--inner-spacing-1);
}
.monthDropdown button {
width: 14ch;
}

View File

@ -0,0 +1,52 @@
import { useDateFormatter } from "@react-aria/i18n";
import type { CalendarState } from "@react-stately/calendar";
export function useYearOptions(state: CalendarState) {
const formatter = useDateFormatter({
year: "numeric",
timeZone: state.timeZone,
});
const years: { value: CalendarState["focusedDate"]; formatted: string }[] =
[];
const YEAR_RANGE = 20;
for (let i = -YEAR_RANGE; i <= YEAR_RANGE; i++) {
const date = state.focusedDate.add({ years: i });
years.push({
value: date,
formatted: formatter.format(date.toDate(state.timeZone)),
});
}
return years;
}
export function useValidMonths(
state: CalendarState,
formatter: Intl.DateTimeFormat,
) {
const months = [];
const numMonths = state.focusedDate.calendar.getMonthsInYear(
state.focusedDate,
);
for (let i = 1; i <= numMonths; i++) {
const date = state.focusedDate.set({ month: i });
// Skip months outside valid range
if (state.minValue && date.compare(state.minValue) < 0) {
continue;
}
if (state.maxValue && date.compare(state.maxValue) > 0) {
continue;
}
months.push(formatter.format(date.toDate(state.timeZone)));
}
return months;
}

View File

@ -79,7 +79,6 @@ export const DatePicker = <T extends DateValue>(props: DatePickerProps<T>) => {
isLoading={isLoading} isLoading={isLoading}
size={size} size={size}
/> />
<FieldError>{errorMessage}</FieldError>
<Popover <Popover
UNSTABLE_portalContainer={root} UNSTABLE_portalContainer={root}
className={clsx(datePickerStyles.popover, popoverClassName)} className={clsx(datePickerStyles.popover, popoverClassName)}
@ -103,6 +102,7 @@ export const DatePicker = <T extends DateValue>(props: DatePickerProps<T>) => {
)} )}
</Dialog> </Dialog>
</Popover> </Popover>
<FieldError>{errorMessage}</FieldError>
</> </>
); );
}} }}

View File

@ -22,7 +22,12 @@ export function FieldLabel(props: LabelProps) {
className={clsx(styles.label)} className={clsx(styles.label)}
elementType="label" elementType="label"
> >
<Text fontWeight={600} size="caption"> <Text
fontWeight={600}
lineClamp={1}
size="caption"
title={children?.toString()}
>
{children} {children}
</Text> </Text>
{Boolean(isRequired) && ( {Boolean(isRequired) && (

View File

@ -3,7 +3,7 @@ import { mergeRefs } from "@react-aria/utils";
import React, { forwardRef, useRef, useState } from "react"; import React, { forwardRef, useRef, useState } from "react";
import { getTypographyClassName } from "@appsmith/wds-theming"; import { getTypographyClassName } from "@appsmith/wds-theming";
import { IconButton, Spinner, type IconProps } from "@appsmith/wds"; import { IconButton, Spinner, type IconProps } from "@appsmith/wds";
import { Group, Input as HeadlessInput } from "react-aria-components"; import { Input as HeadlessInput } from "react-aria-components";
import styles from "./styles.module.css"; import styles from "./styles.module.css";
import type { InputProps } from "./types"; import type { InputProps } from "./types";
@ -49,7 +49,7 @@ function _Input(props: InputProps, ref: React.Ref<HTMLInputElement>) {
})(); })();
return ( return (
<Group className={styles.inputGroup}> <div className={styles.inputGroup}>
{Boolean(prefix) && ( {Boolean(prefix) && (
<span data-input-prefix onClick={() => localRef.current?.focus()}> <span data-input-prefix onClick={() => localRef.current?.focus()}>
{prefix} {prefix}
@ -74,7 +74,7 @@ function _Input(props: InputProps, ref: React.Ref<HTMLInputElement>) {
{suffix} {suffix}
</span> </span>
)} )}
</Group> </div>
); );
} }

View File

@ -27,6 +27,7 @@
padding-block: var(--inner-spacing-1); padding-block: var(--inner-spacing-1);
padding-inline: var(--inner-spacing-2); padding-inline: var(--inner-spacing-2);
box-sizing: content-box; box-sizing: content-box;
cursor: inherit;
} }
.inputGroup:has([data-input-prefix]) .input { .inputGroup:has([data-input-prefix]) .input {
@ -63,10 +64,18 @@
margin-inline-start: var(--inner-spacing-2); margin-inline-start: var(--inner-spacing-2);
} }
.inputGroup:has(.input[data-size="small"]) [data-input-prefix] {
margin-inline-start: 0;
}
.inputGroup [data-input-suffix] { .inputGroup [data-input-suffix] {
margin-inline-end: var(--inner-spacing-2); margin-inline-end: var(--inner-spacing-2);
} }
.inputGroup:has(.input[data-size="small"]) [data-input-suffix] {
margin-inline-end: 0;
}
.inputGroup :is([data-input-suffix], [data-input-prefix]) button { .inputGroup :is([data-input-suffix], [data-input-prefix]) button {
border-radius: calc( border-radius: calc(
var(--border-radius-elevation-3) - var(--inner-spacing-1) var(--border-radius-elevation-3) - var(--inner-spacing-1)
@ -84,7 +93,7 @@
* HOVERED * HOVERED
* ---------------------------------------------------------------------------- * ----------------------------------------------------------------------------
*/ */
.inputGroup[data-hovered]:has( .inputGroup:is([data-hovered], :has([data-hovered])):has(
> .input:not( > .input:not(
:is( :is(
[data-focused], [data-focused],
@ -111,6 +120,9 @@
.inputGroup input[data-readonly] { .inputGroup input[data-readonly] {
padding-inline: 0; padding-inline: 0;
text-overflow: ellipsis;
white-space: nowrap;
cursor: text;
} }
/** Reason for doing this is because for readonly inputs, we want focus state to be wider than the component width */ /** Reason for doing this is because for readonly inputs, we want focus state to be wider than the component width */
@ -143,8 +155,9 @@
* DISABLED * DISABLED
* ---------------------------------------------------------------------------- * ----------------------------------------------------------------------------
*/ */
.inputGroup:has(> .input[data-disabled]), .inputGroup:has(
.inputGroup:has(> .input:has(~ input[data-disabled])) { :is(.input[data-disabled], .input:has(~ input[data-disabled]))
) {
cursor: not-allowed; cursor: not-allowed;
box-shadow: none; box-shadow: none;
} }

View File

@ -2,7 +2,7 @@ import clsx from "clsx";
import React from "react"; import React from "react";
import { Icon, Spinner, textInputStyles } from "@appsmith/wds"; import { Icon, Spinner, textInputStyles } from "@appsmith/wds";
import { getTypographyClassName } from "@appsmith/wds-theming"; import { getTypographyClassName } from "@appsmith/wds-theming";
import { Button, Group, SelectValue } from "react-aria-components"; import { Button, SelectValue } from "react-aria-components";
import styles from "./styles.module.css"; import styles from "./styles.module.css";
import type { SelectProps } from "./types"; import type { SelectProps } from "./types";
@ -19,9 +19,7 @@ export const SelectTrigger: React.FC<SelectTriggerProps> = (props) => {
const { isDisabled, isInvalid, isLoading, placeholder, size } = props; const { isDisabled, isInvalid, isLoading, placeholder, size } = props;
return ( return (
<Group <div className={clsx(textInputStyles.inputGroup, styles.selectInputGroup)}>
className={clsx(textInputStyles.inputGroup, styles.selectInputGroup)}
>
<Button <Button
className={clsx(textInputStyles.input, styles.selectTriggerButton)} className={clsx(textInputStyles.input, styles.selectTriggerButton)}
data-invalid={Boolean(isInvalid) ? "" : undefined} data-invalid={Boolean(isInvalid) ? "" : undefined}
@ -44,6 +42,6 @@ export const SelectTrigger: React.FC<SelectTriggerProps> = (props) => {
)} )}
</span> </span>
</Button> </Button>
</Group> </div>
); );
}; };

View File

@ -45,6 +45,7 @@ const _Text = (props: TextProps, ref: Ref<HTMLDivElement>) => {
fontStyle: isItalic ? "italic" : "normal", fontStyle: isItalic ? "italic" : "normal",
wordBreak, wordBreak,
textAlign, textAlign,
whiteSpace: "pre-wrap",
...style, ...style,
}} }}
{...rest} {...rest}

View File

@ -56,7 +56,7 @@ class WDSBaseInputWidget<
static getMetaPropertiesMap(): Record<string, any> { static getMetaPropertiesMap(): Record<string, any> {
return { return {
rawText: undefined, rawText: undefined,
parsedText: undefined, text: undefined,
isFocused: false, isFocused: false,
isDirty: false, isDirty: false,
}; };

View File

@ -4,14 +4,14 @@ export const autocompleteConfig = {
"!doc": "!doc":
"An input text field is used to capture a currency value. Inputs are used in forms and can have custom validations.", "An input text field is used to capture a currency value. Inputs are used in forms and can have custom validations.",
"!url": "https://docs.appsmith.com/widget-reference/currency-input", "!url": "https://docs.appsmith.com/widget-reference/currency-input",
parsedText: { text: {
"!type": "string", "!type": "string",
"!doc": "The formatted text value of the input", "!doc": "The formatted text value of the input",
"!url": "https://docs.appsmith.com/widget-reference/currency-input", "!url": "https://docs.appsmith.com/widget-reference/currency-input",
}, },
rawText: { rawText: {
"!type": "number", "!type": "number",
"!doc": "The value of the input", "!doc": "The raw text value of the input",
"!url": "https://docs.appsmith.com/widget-reference/currency-input", "!url": "https://docs.appsmith.com/widget-reference/currency-input",
}, },
isValid: "bool", isValid: "bool",

View File

@ -130,7 +130,7 @@ class WDSCurrencyInputWidget extends WDSBaseInputWidget<
static getMetaPropertiesMap(): Record<string, any> { static getMetaPropertiesMap(): Record<string, any> {
return _.merge(super.getMetaPropertiesMap(), { return _.merge(super.getMetaPropertiesMap(), {
rawText: "", rawText: "",
parsedText: "", text: "",
currencyCode: undefined, currencyCode: undefined,
}); });
} }
@ -139,7 +139,7 @@ class WDSCurrencyInputWidget extends WDSBaseInputWidget<
return _.merge(super.getDefaultPropertiesMap(), { return _.merge(super.getDefaultPropertiesMap(), {
currencyCode: "defaultCurrencyCode", currencyCode: "defaultCurrencyCode",
rawText: "defaultText", rawText: "defaultText",
parsedText: "defaultText", text: "defaultText",
}); });
} }
@ -153,9 +153,9 @@ class WDSCurrencyInputWidget extends WDSBaseInputWidget<
componentDidUpdate(prevProps: CurrencyInputWidgetProps) { componentDidUpdate(prevProps: CurrencyInputWidgetProps) {
if ( if (
prevProps.text !== this.props.parsedText && prevProps.text !== this.props.text &&
!this.props.isFocused && !this.props.isFocused &&
this.props.parsedText === String(this.props.defaultText) this.props.text === String(this.props.defaultText)
) { ) {
this.formatText(); this.formatText();
} }
@ -192,7 +192,7 @@ class WDSCurrencyInputWidget extends WDSBaseInputWidget<
Sentry.captureException(e); Sentry.captureException(e);
} }
this.props.updateWidgetMetaProperty("parsedText", String(formattedValue)); this.props.updateWidgetMetaProperty("text", String(formattedValue));
this.props.updateWidgetMetaProperty("rawText", value, { this.props.updateWidgetMetaProperty("rawText", value, {
triggerPropertyName: "onTextChanged", triggerPropertyName: "onTextChanged",
@ -214,13 +214,13 @@ class WDSCurrencyInputWidget extends WDSBaseInputWidget<
try { try {
if (isFocused) { if (isFocused) {
const text = this.props.parsedText || ""; const text = this.props.text || "";
const deFormattedValue = text.replace( const deFormattedValue = text.replace(
new RegExp("\\" + getLocaleThousandSeparator(), "g"), new RegExp("\\" + getLocaleThousandSeparator(), "g"),
"", "",
); );
this.props.updateWidgetMetaProperty("parsedText", deFormattedValue); this.props.updateWidgetMetaProperty("text", deFormattedValue);
this.props.updateWidgetMetaProperty("isFocused", isFocused, { this.props.updateWidgetMetaProperty("isFocused", isFocused, {
triggerPropertyName: "onFocus", triggerPropertyName: "onFocus",
dynamicString: this.props.onFocus, dynamicString: this.props.onFocus,
@ -229,13 +229,13 @@ class WDSCurrencyInputWidget extends WDSBaseInputWidget<
}, },
}); });
} else { } else {
if (this.props.parsedText) { if (this.props.text) {
const formattedValue = formatCurrencyNumber( const formattedValue = formatCurrencyNumber(
this.props.decimals, this.props.decimals,
this.props.parsedText, this.props.text,
); );
this.props.updateWidgetMetaProperty("parsedText", formattedValue); this.props.updateWidgetMetaProperty("text", formattedValue);
} }
this.props.updateWidgetMetaProperty("isFocused", isFocused, { this.props.updateWidgetMetaProperty("isFocused", isFocused, {
@ -249,7 +249,7 @@ class WDSCurrencyInputWidget extends WDSBaseInputWidget<
} catch (e) { } catch (e) {
log.error(e); log.error(e);
Sentry.captureException(e); Sentry.captureException(e);
this.props.updateWidgetMetaProperty("parsedText", this.props.parsedText); this.props.updateWidgetMetaProperty("text", this.props.text);
} }
super.onFocusChange(!!isFocused); super.onFocusChange(!!isFocused);
@ -294,13 +294,13 @@ class WDSCurrencyInputWidget extends WDSBaseInputWidget<
}; };
isTextFormatted = () => { isTextFormatted = () => {
return this.props.parsedText.includes(getLocaleThousandSeparator()); return this.props.text.includes(getLocaleThousandSeparator());
}; };
formatText() { formatText() {
if (!!this.props.parsedText && !this.isTextFormatted()) { if (!!this.props.text && !this.isTextFormatted()) {
try { try {
const floatVal = parseFloat(this.props.parsedText); const floatVal = parseFloat(this.props.text);
const formattedValue = Intl.NumberFormat(getLocale(), { const formattedValue = Intl.NumberFormat(getLocale(), {
style: "decimal", style: "decimal",
@ -308,7 +308,7 @@ class WDSCurrencyInputWidget extends WDSBaseInputWidget<
maximumFractionDigits: this.props.decimals, maximumFractionDigits: this.props.decimals,
}).format(floatVal); }).format(floatVal);
this.props.updateWidgetMetaProperty("parsedText", formattedValue); this.props.updateWidgetMetaProperty("text", formattedValue);
} catch (e) { } catch (e) {
log.error(e); log.error(e);
Sentry.captureException(e); Sentry.captureException(e);

View File

@ -4,9 +4,14 @@ export const autocompleteConfig = {
"!doc": "!doc":
"An input text field is used to capture a users textual input such as their names, numbers, emails etc. Inputs are used in forms and can have custom validations.", "An input text field is used to capture a users textual input such as their names, numbers, emails etc. Inputs are used in forms and can have custom validations.",
"!url": "https://docs.appsmith.com/widget-reference/input", "!url": "https://docs.appsmith.com/widget-reference/input",
parsedText: { text: {
"!type": "string", "!type": "string",
"!doc": "The text value of the input", "!doc": "The parsed text value of the input",
"!url": "https://docs.appsmith.com/widget-reference/input",
},
rawText: {
"!type": "string",
"!doc": "The raw text value of the input",
"!url": "https://docs.appsmith.com/widget-reference/input", "!url": "https://docs.appsmith.com/widget-reference/input",
}, },
isValid: "bool", isValid: "bool",

View File

@ -19,7 +19,7 @@ export const settersConfig = {
setValue: { setValue: {
path: "defaultText", path: "defaultText",
type: "string", type: "string",
accessor: "parsedText", accessor: "text",
}, },
}, },
}; };

View File

@ -69,11 +69,11 @@ export const validateInput = (props: InputWidgetProps): Validation => {
maxChars, maxChars,
maxNum, maxNum,
minNum, minNum,
parsedText,
rawText, rawText,
text,
} = props; } = props;
if (isDirty && isRequired && !isNil(parsedText) && parsedText.length === 0) { if (isDirty && isRequired && !isNil(text) && text.length === 0) {
return { return {
validationStatus: "invalid", validationStatus: "invalid",
errorMessage: createMessage(FIELD_REQUIRED_ERROR), errorMessage: createMessage(FIELD_REQUIRED_ERROR),
@ -81,7 +81,7 @@ export const validateInput = (props: InputWidgetProps): Validation => {
} }
if (isInputTypeSingleLineOrMultiLine(inputType) && maxChars) { if (isInputTypeSingleLineOrMultiLine(inputType) && maxChars) {
if (parsedText && parsedText.toString().length > maxChars) { if (text && text.toString().length > maxChars) {
return { return {
validationStatus: "invalid", validationStatus: "invalid",
errorMessage: createMessage(INPUT_TEXT_MAX_CHAR_ERROR, maxChars), errorMessage: createMessage(INPUT_TEXT_MAX_CHAR_ERROR, maxChars),
@ -97,16 +97,16 @@ export const validateInput = (props: InputWidgetProps): Validation => {
}; };
} }
if (rawText !== "" && isNumber(parsedText)) { if (rawText !== "" && isNumber(text)) {
// check the default text is neither greater than max nor less than min value. // check the default text is neither greater than max nor less than min value.
if (!isNil(minNum) && minNum > parsedText) { if (!isNil(minNum) && minNum > text) {
return { return {
validationStatus: "invalid", validationStatus: "invalid",
errorMessage: createMessage(INPUT_DEFAULT_TEXT_MIN_NUM_ERROR), errorMessage: createMessage(INPUT_DEFAULT_TEXT_MIN_NUM_ERROR),
}; };
} }
if (!isNil(maxNum) && maxNum < parsedText) { if (!isNil(maxNum) && maxNum < text) {
return { return {
validationStatus: "invalid", validationStatus: "invalid",
errorMessage: createMessage(INPUT_DEFAULT_TEXT_MAX_NUM_ERROR), errorMessage: createMessage(INPUT_DEFAULT_TEXT_MAX_NUM_ERROR),

View File

@ -62,14 +62,14 @@ class WDSInputWidget extends WDSBaseInputWidget<InputWidgetProps, WidgetState> {
static getMetaPropertiesMap(): Record<string, any> { static getMetaPropertiesMap(): Record<string, any> {
return merge(super.getMetaPropertiesMap(), { return merge(super.getMetaPropertiesMap(), {
rawText: "", rawText: "",
parsedText: "", text: "",
}); });
} }
static getDefaultPropertiesMap(): Record<string, string> { static getDefaultPropertiesMap(): Record<string, string> {
return { return {
rawText: "defaultText", rawText: "defaultText",
parsedText: "defaultText", text: "defaultText",
}; };
} }
@ -157,17 +157,17 @@ class WDSInputWidget extends WDSBaseInputWidget<InputWidgetProps, WidgetState> {
if ( if (
prevProps.rawText !== this.props.rawText && prevProps.rawText !== this.props.rawText &&
this.props.rawText !== toString(this.props.parsedText) this.props.rawText !== toString(this.props.text)
) { ) {
pushBatchMetaUpdates( pushBatchMetaUpdates(
"parsedText", "text",
parseText(this.props.rawText, this.props.inputType), parseText(this.props.rawText, this.props.inputType),
); );
} }
if (prevProps.inputType !== this.props.inputType) { if (prevProps.inputType !== this.props.inputType) {
pushBatchMetaUpdates( pushBatchMetaUpdates(
"parsedText", "text",
parseText(this.props.rawText, this.props.inputType), parseText(this.props.rawText, this.props.inputType),
); );
} }
@ -190,7 +190,7 @@ class WDSInputWidget extends WDSBaseInputWidget<InputWidgetProps, WidgetState> {
// derived properties won't work as expected inside a List widget. // derived properties won't work as expected inside a List widget.
// TODO(Balaji): Once we refactor the List widget, need to conver // TODO(Balaji): Once we refactor the List widget, need to conver
// text to a derived property. // text to a derived property.
pushBatchMetaUpdates("parsedText", parseText(value, this.props.inputType)); pushBatchMetaUpdates("text", parseText(value, this.props.inputType));
pushBatchMetaUpdates("rawText", value, { pushBatchMetaUpdates("rawText", value, {
triggerPropertyName: "onTextChanged", triggerPropertyName: "onTextChanged",
@ -211,7 +211,7 @@ class WDSInputWidget extends WDSBaseInputWidget<InputWidgetProps, WidgetState> {
const { commitBatchMetaUpdates, pushBatchMetaUpdates } = this.props; const { commitBatchMetaUpdates, pushBatchMetaUpdates } = this.props;
pushBatchMetaUpdates("rawText", ""); pushBatchMetaUpdates("rawText", "");
pushBatchMetaUpdates("parsedText", parseText("", this.props.inputType)); pushBatchMetaUpdates("text", parseText("", this.props.inputType));
commitBatchMetaUpdates(); commitBatchMetaUpdates();
}; };

View File

@ -4,7 +4,7 @@ export const autocompleteConfig = {
"!doc": "!doc":
"An input text field is used to capture a phone number. Inputs are used in forms and can have custom validations.", "An input text field is used to capture a phone number. Inputs are used in forms and can have custom validations.",
"!url": "https://docs.appsmith.com/widget-reference/phone-input", "!url": "https://docs.appsmith.com/widget-reference/phone-input",
parsedText: { text: {
"!type": "string", "!type": "string",
"!doc": "The formatted text value of the input", "!doc": "The formatted text value of the input",
"!url": "https://docs.appsmith.com/widget-reference/phone-input", "!url": "https://docs.appsmith.com/widget-reference/phone-input",

View File

@ -5,7 +5,7 @@ import { ISDCodeOptions } from "constants/ISDCodes_v2";
// TODO: Fix this the next time the file is edited // TODO: Fix this the next time the file is edited
// eslint-disable-next-line @typescript-eslint/no-explicit-any // eslint-disable-next-line @typescript-eslint/no-explicit-any
export function validateInput(props: any) { export function validateInput(props: any) {
const value = props.parsedText ?? ""; const value = props.text ?? "";
const isInvalid = "isValid" in props && !props.isValid && !!props.isDirty; const isInvalid = "isValid" in props && !props.isValid && !!props.isDirty;
// TODO: Fix this the next time the file is edited // TODO: Fix this the next time the file is edited

View File

@ -117,7 +117,7 @@ class WDSPhoneInputWidget extends WDSBaseInputWidget<
static getMetaPropertiesMap(): Record<string, any> { static getMetaPropertiesMap(): Record<string, any> {
return merge(super.getMetaPropertiesMap(), { return merge(super.getMetaPropertiesMap(), {
rawText: "", rawText: "",
parsedText: "", text: "",
dialCode: undefined, dialCode: undefined,
}); });
} }
@ -126,7 +126,7 @@ class WDSPhoneInputWidget extends WDSBaseInputWidget<
return merge(super.getDefaultPropertiesMap(), { return merge(super.getDefaultPropertiesMap(), {
dialCode: "defaultDialCode", dialCode: "defaultDialCode",
rawText: "defaultText", rawText: "defaultText",
parsedText: "defaultText", text: "defaultText",
}); });
} }
@ -160,7 +160,7 @@ class WDSPhoneInputWidget extends WDSBaseInputWidget<
const formattedValue = this.getFormattedPhoneNumber(this.props.rawText); const formattedValue = this.getFormattedPhoneNumber(this.props.rawText);
this.props.updateWidgetMetaProperty("rawText", this.props.rawText); this.props.updateWidgetMetaProperty("rawText", this.props.rawText);
this.props.updateWidgetMetaProperty("parsedText", formattedValue); this.props.updateWidgetMetaProperty("text", formattedValue);
} catch (e) { } catch (e) {
log.error(e); log.error(e);
Sentry.captureException(e); Sentry.captureException(e);
@ -176,24 +176,22 @@ class WDSPhoneInputWidget extends WDSBaseInputWidget<
if (prevProps.allowFormatting !== this.props.allowFormatting) { if (prevProps.allowFormatting !== this.props.allowFormatting) {
const formattedValue = this.getFormattedPhoneNumber(this.props.rawText); const formattedValue = this.getFormattedPhoneNumber(this.props.rawText);
this.props.updateWidgetMetaProperty("parsedText", formattedValue); this.props.updateWidgetMetaProperty("text", formattedValue);
} }
// When the default text changes // When the default text changes
if ( if (
prevProps.parsedText !== this.props.parsedText && prevProps.text !== this.props.text &&
this.props.parsedText === this.props.defaultText this.props.text === this.props.defaultText
) { ) {
const formattedValue = this.getFormattedPhoneNumber( const formattedValue = this.getFormattedPhoneNumber(this.props.text);
this.props.parsedText,
);
if (formattedValue) { if (formattedValue) {
this.props.updateWidgetMetaProperty( this.props.updateWidgetMetaProperty(
"rawText", "rawText",
parseIncompletePhoneNumber(formattedValue), parseIncompletePhoneNumber(formattedValue),
); );
this.props.updateWidgetMetaProperty("parsedText", formattedValue); this.props.updateWidgetMetaProperty("text", formattedValue);
} }
} }
@ -214,7 +212,7 @@ class WDSPhoneInputWidget extends WDSBaseInputWidget<
if (this.props.rawText && this.props.allowFormatting) { if (this.props.rawText && this.props.allowFormatting) {
const formattedValue = this.getFormattedPhoneNumber(this.props.rawText); const formattedValue = this.getFormattedPhoneNumber(this.props.rawText);
this.props.updateWidgetMetaProperty("parsedText", formattedValue); this.props.updateWidgetMetaProperty("text", formattedValue);
} }
}; };
@ -222,7 +220,7 @@ class WDSPhoneInputWidget extends WDSBaseInputWidget<
let formattedValue; let formattedValue;
// Don't format, as value is typed, when user is deleting // Don't format, as value is typed, when user is deleting
if (value && value.length > this.props.parsedText?.length) { if (value && value.length > this.props.text?.length) {
formattedValue = this.getFormattedPhoneNumber(value); formattedValue = this.getFormattedPhoneNumber(value);
} else { } else {
formattedValue = value; formattedValue = value;
@ -232,7 +230,7 @@ class WDSPhoneInputWidget extends WDSBaseInputWidget<
"rawText", "rawText",
parseIncompletePhoneNumber(formattedValue), parseIncompletePhoneNumber(formattedValue),
); );
this.props.updateWidgetMetaProperty("parsedText", formattedValue, { this.props.updateWidgetMetaProperty("text", formattedValue, {
triggerPropertyName: "onTextChanged", triggerPropertyName: "onTextChanged",
dynamicString: this.props.onTextChanged, dynamicString: this.props.onTextChanged,
event: { event: {
@ -300,7 +298,7 @@ class WDSPhoneInputWidget extends WDSBaseInputWidget<
}; };
getWidgetView() { getWidgetView() {
const rawText = this.props.parsedText ?? ""; const rawText = this.props.text ?? "";
const validation = validateInput(this.props); const validation = validateInput(this.props);