Skip to content

Commit

Permalink
fix(input, input-number): support setting value property to Infinity (#…
Browse files Browse the repository at this point in the history
…8547)

**Related Issue:** #7866

## Summary

- Setting the value of `calcite-input` and `calcite-input-number` to
`Infinity` or `-Infinity` no longer causes errors
- `Infinity` or `-Infinity` will be displayed in the input.
- Entering additional characters is prevented, and pressing Backspace or
Delete will clear the value.
  • Loading branch information
benelan authored Jan 10, 2024
1 parent 6b086e8 commit f6ac698
Show file tree
Hide file tree
Showing 6 changed files with 136 additions and 28 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -723,6 +723,27 @@ describe("calcite-input-number", () => {
const input = await page.find("calcite-input-number >>> input");
expect(await input.getProperty("value")).toBe("2");
});

it("Setting the value to -Infinity prevents typing additional numbers and clears the value on Backspace or Delete", async () => {
const page = await newE2EPage();
await page.setContent(html`<calcite-input-number></calcite-input-number>`);
const input = await page.find("calcite-input-number");

await input.callMethod("setFocus");
await page.waitForChanges();

input.setProperty("value", "-Infinity");
await page.waitForChanges();
expect(await input.getProperty("value")).toBe("-Infinity");

await typeNumberValue(page, "123");
await page.waitForChanges();
expect(await input.getProperty("value")).toBe("-Infinity");

await page.keyboard.press("Backspace");
await page.waitForChanges();
expect(await input.getProperty("value")).toBe("");
});
});

describe("emits events when value is modified", () => {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -92,6 +92,8 @@ export const darkModeRTL_TestOnly = (): string => html`
`;
darkModeRTL_TestOnly.parameters = { modes: modesDarkDefault };

export const Infinity_TestOnly = (): string => html`<calcite-input-number value="Infinity"></calcite-input-number>`;

export const mediumIconForLargeInputStyling_TestOnly = (): string => html`
<calcite-input-number number-button-type="vertical" lang="ar-EG" value="123456" scale="l"></calcite-input-number
><calcite-input-number
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -127,8 +127,7 @@ export class InputNumber
*
* When not set, the component will be associated with its ancestor form element, if any.
*/
@Prop({ reflect: true })
form: string;
@Prop({ reflect: true }) form: string;

/**
* When `true`, number values are displayed with a group separator corresponding to the language and country format.
Expand Down Expand Up @@ -321,6 +320,12 @@ export class InputNumber
@Watch("value")
valueWatcher(newValue: string, previousValue: string): void {
if (!this.userChangedValue) {
if (newValue === "Infinity" || newValue === "-Infinity") {
this.displayedValue = newValue;
this.previousEmittedNumberValue = newValue;
return;
}

this.setNumberValue({
origin: "direct",
previousValue,
Expand Down Expand Up @@ -403,7 +408,7 @@ export class InputNumber

@State() defaultMessages: InputNumberMessages;

@State() localizedValue: string;
@State() displayedValue: string;

@State() slottedActionElDisabledInternally = false;

Expand All @@ -428,10 +433,17 @@ export class InputNumber
this.setPreviousNumberValue(this.value);

this.warnAboutInvalidNumberValue(this.value);
this.setNumberValue({
origin: "connected",
value: isValidNumber(this.value) ? this.value : "",
});

if (this.value === "Infinity" || this.value === "-Infinity") {
this.displayedValue = this.value;
this.previousEmittedNumberValue = this.value;
} else {
this.setNumberValue({
origin: "connected",
value: isValidNumber(this.value) ? this.value : "",
});
}

this.mutationObserver?.observe(this.el, { childList: true });
this.setDisabledAction();
this.el.addEventListener(internalHiddenInputInputEvent, this.onHiddenFormInputInput);
Expand Down Expand Up @@ -552,6 +564,11 @@ export class InputNumber
nativeEvent: KeyboardEvent | MouseEvent,
): void {
const { value } = this;

if (value === "Infinity" || value === "-Infinity") {
return;
}

const adjustment = direction === "up" ? 1 : -1;
const stepHandleInteger =
this.integer && this.step !== "any" ? Math.round(this.step) : this.step;
Expand Down Expand Up @@ -624,6 +641,11 @@ export class InputNumber
if (this.disabled || this.readOnly) {
return;
}

if (this.value === "Infinity" || this.value === "-Infinity") {
return;
}

const value = (nativeEvent.target as HTMLInputElement).value;
numberStringFormatter.numberFormatOptions = {
locale: this.effectiveLocale,
Expand All @@ -643,7 +665,7 @@ export class InputNumber
origin: "user",
value: parseNumberString(delocalizedValue),
});
this.childNumberEl.value = this.localizedValue;
this.childNumberEl.value = this.displayedValue;
} else {
this.setNumberValue({
nativeEvent,
Expand All @@ -657,6 +679,15 @@ export class InputNumber
if (this.disabled || this.readOnly) {
return;
}

if (this.value === "Infinity" || this.value === "-Infinity") {
event.preventDefault();
if (event.key === "Backspace" || event.key === "Delete") {
this.clearInputValue(event);
}
return;
}

if (event.key === "ArrowUp") {
/* prevent default behavior of moving cursor to the beginning of the input when holding down ArrowUp */
event.preventDefault();
Expand Down Expand Up @@ -888,7 +919,7 @@ export class InputNumber
}

// adds localized trailing decimal separator
this.localizedValue =
this.displayedValue =
hasTrailingDecimalSeparator && isValueDeleted
? `${newLocalizedValue}${numberStringFormatter.decimal}`
: newLocalizedValue;
Expand All @@ -909,7 +940,7 @@ export class InputNumber
const calciteInputNumberInputEvent = this.calciteInputNumberInput.emit();
if (calciteInputNumberInputEvent.defaultPrevented) {
this.value = this.previousValue;
this.localizedValue = numberStringFormatter.localize(this.previousValue);
this.displayedValue = numberStringFormatter.localize(this.previousValue);
} else if (committing) {
this.emitChangeIfUserModified();
}
Expand Down Expand Up @@ -1033,7 +1064,7 @@ export class InputNumber
placeholder={this.placeholder || ""}
readOnly={this.readOnly}
type="text"
value={this.localizedValue}
value={this.displayedValue}
// eslint-disable-next-line react/jsx-sort-props -- ref should be last so node attrs/props are in sync (see https://github.com/Esri/calcite-design-system/pull/6530)
ref={this.setChildNumberElRef}
/>
Expand Down
31 changes: 26 additions & 5 deletions packages/calcite-components/src/components/input/input.e2e.ts
Original file line number Diff line number Diff line change
Expand Up @@ -751,13 +751,34 @@ describe("calcite-input", () => {

await input.callMethod("setFocus");
await page.waitForChanges();
await input.setProperty("value", "not a random value");
input.setProperty("value", "not a random value");
await page.keyboard.press("Tab");
await page.waitForChanges();

expect(inputEventSpy).not.toHaveReceivedEvent();
expect(changeEventSpy).not.toHaveReceivedEvent();
});

it("Setting the value to Infinity prevents typing additional numbers and clears the value on Backspace or Delete", async () => {
const page = await newE2EPage();
await page.setContent(html`<calcite-input type="number"></calcite-input>`);
const input = await page.find("calcite-input");

await input.callMethod("setFocus");
await page.waitForChanges();

input.setProperty("value", "Infinity");
await page.waitForChanges();
expect(await input.getProperty("value")).toBe("Infinity");

await typeNumberValue(page, "123");
await page.waitForChanges();
expect(await input.getProperty("value")).toBe("Infinity");

await page.keyboard.press("Backspace");
await page.waitForChanges();
expect(await input.getProperty("value")).toBe("");
});
});

describe("emits events when value is modified", () => {
Expand Down Expand Up @@ -1923,7 +1944,7 @@ describe("calcite-input", () => {
expect(await button.getProperty("disabled")).toBe(true);
expect(await input.getProperty("disabled")).toBe(false);

await input.setProperty("disabled", true);
input.setProperty("disabled", true);
await input.callMethod("setFocus");
await page.waitForChanges();
await page.keyboard.type("2");
Expand All @@ -1932,7 +1953,7 @@ describe("calcite-input", () => {
expect(await button.getProperty("disabled")).toBe(true);
expect(await input.getProperty("disabled")).toBe(true);

await input.setProperty("disabled", false);
input.setProperty("disabled", false);
await page.waitForChanges();
await input.callMethod("setFocus");
await page.waitForChanges();
Expand All @@ -1942,7 +1963,7 @@ describe("calcite-input", () => {
expect(await button.getProperty("disabled")).toBe(true);
expect(await input.getProperty("disabled")).toBe(false);

await button.setProperty("disabled", false);
button.setProperty("disabled", false);
await page.waitForChanges();
await input.callMethod("setFocus");
await page.waitForChanges();
Expand All @@ -1952,7 +1973,7 @@ describe("calcite-input", () => {
expect(await button.getProperty("disabled")).toBe(false);
expect(await input.getProperty("disabled")).toBe(false);

await input.setProperty("disabled", true);
input.setProperty("disabled", true);
await page.waitForChanges();
await input.callMethod("setFocus");
await page.waitForChanges();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -129,8 +129,12 @@ export const darkModeRTL_TestOnly = (): string => html`
</calcite-label>
</div>
`;

darkModeRTL_TestOnly.parameters = { modes: modesDarkDefault };

export const negativeInfinity_TestOnly = (): string =>
html` <calcite-input type="number" value="-Infinity"></calcite-input>`;

export const arabicLocaleWithLatinNumberingSystem_TestOnly = (): string =>
html` <calcite-input type="number" lang="ar-EG" value="123456"></calcite-input>`;

Expand Down
53 changes: 41 additions & 12 deletions packages/calcite-components/src/components/input/input.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -128,8 +128,7 @@ export class Input
*
* When not set, the component will be associated with its ancestor form element, if any.
*/
@Prop({ reflect: true })
form: string;
@Prop({ reflect: true }) form: string;

/**
* When `true`, number values are displayed with a group separator corresponding to the language and country format.
Expand Down Expand Up @@ -374,6 +373,12 @@ export class Input
@Watch("value")
valueWatcher(newValue: string, previousValue: string): void {
if (!this.userChangedValue) {
if (this.type === "number" && (newValue === "Infinity" || newValue === "-Infinity")) {
this.displayedValue = newValue;
this.previousEmittedValue = newValue;
return;
}

this.setValue({
origin: "direct",
previousValue,
Expand Down Expand Up @@ -464,7 +469,7 @@ export class Input
updateMessages(this, this.effectiveLocale);
}

@State() localizedValue: string;
@State() displayedValue: string;

@State() slottedActionElDisabledInternally = false;

Expand All @@ -490,11 +495,16 @@ export class Input
this.setPreviousValue(this.value);

if (this.type === "number") {
this.warnAboutInvalidNumberValue(this.value);
this.setValue({
origin: "connected",
value: isValidNumber(this.value) ? this.value : "",
});
if (this.value === "Infinity" || this.value === "-Infinity") {
this.displayedValue = this.value;
this.previousEmittedValue = this.value;
} else {
this.warnAboutInvalidNumberValue(this.value);
this.setValue({
origin: "connected",
value: isValidNumber(this.value) ? this.value : "",
});
}
}

this.mutationObserver?.observe(this.el, { childList: true });
Expand Down Expand Up @@ -628,6 +638,11 @@ export class Input
nativeEvent: KeyboardEvent | MouseEvent,
): void {
const { value } = this;

if (value === "Infinity" || value === "-Infinity") {
return;
}

const adjustment = direction === "up" ? 1 : -1;
const inputStep = this.step === "any" ? 1 : Math.abs(this.step || 1);
const inputVal = new BigDecimal(value !== "" ? value : "0");
Expand Down Expand Up @@ -724,6 +739,11 @@ export class Input
if (this.disabled || this.readOnly) {
return;
}

if (this.value === "Infinity" || this.value === "-Infinity") {
return;
}

const value = (nativeEvent.target as HTMLInputElement).value;
numberStringFormatter.numberFormatOptions = {
locale: this.effectiveLocale,
Expand All @@ -740,7 +760,7 @@ export class Input
origin: "user",
value: parseNumberString(delocalizedValue),
});
this.childNumberEl.value = this.localizedValue;
this.childNumberEl.value = this.displayedValue;
} else {
this.setValue({
nativeEvent,
Expand All @@ -754,6 +774,15 @@ export class Input
if (this.type !== "number" || this.disabled || this.readOnly) {
return;
}

if (this.value === "Infinity" || this.value === "-Infinity") {
event.preventDefault();
if (event.key === "Backspace" || event.key === "Delete") {
this.clearInputValue(event);
}
return;
}

if (event.key === "ArrowUp") {
/* prevent default behavior of moving cursor to the beginning of the input when holding down ArrowUp */
event.preventDefault();
Expand Down Expand Up @@ -994,7 +1023,7 @@ export class Input
}

// adds localized trailing decimal separator
this.localizedValue =
this.displayedValue =
hasTrailingDecimalSeparator && isValueDeleted
? `${newLocalizedValue}${numberStringFormatter.decimal}`
: newLocalizedValue;
Expand All @@ -1017,7 +1046,7 @@ export class Input
const calciteInputInputEvent = this.calciteInputInput.emit();
if (calciteInputInputEvent.defaultPrevented) {
this.value = this.previousValue;
this.localizedValue =
this.displayedValue =
this.type === "number"
? numberStringFormatter.localize(this.previousValue)
: this.previousValue;
Expand Down Expand Up @@ -1148,7 +1177,7 @@ export class Input
placeholder={this.placeholder || ""}
readOnly={this.readOnly}
type="text"
value={this.localizedValue}
value={this.displayedValue}
// eslint-disable-next-line react/jsx-sort-props -- ref should be last so node attrs/props are in sync (see https://github.com/Esri/calcite-design-system/pull/6530)
ref={this.setChildNumberElRef}
/>
Expand Down

0 comments on commit f6ac698

Please sign in to comment.