diff --git a/CHANGELOG.md b/CHANGELOG.md
index 330176d7b..526ccdc56 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -13,6 +13,7 @@ The following is a curated list of changes in the Enact limestone module, newest
- `limestone/Alert` text container to fit the width of its text and center-align
- `limestone/Chips.Chip` styling to match the latest GUI
+- `limestone/Input` to show the submit button for separated number inputs by default
## [1.10.2] - 2026-07-09
diff --git a/Input/Input.js b/Input/Input.js
index c79cb4530..d7d8c0331 100644
--- a/Input/Input.js
+++ b/Input/Input.js
@@ -166,8 +166,8 @@ const InputPopupBase = kind({
*
* Overridden by `length` value.
*
- * When smaller than `maxLength`, number type inputs will show a submit button and will not
- * auto-submit when the length reaches `maxLength`. Defaults to the `maxLength` value.
+ * Defaults to the `maxLength` value. The submit button is disabled while the value is
+ * shorter than `minLength`.
*
* @type {Number}
* @public
@@ -185,6 +185,13 @@ const InputPopupBase = kind({
/**
* Omits the submit button.
*
+ * When `true` for separated number inputs where `minLength` equals `maxLength`, the input
+ * auto-submits when the length reaches `maxLength`.
+ *
+ * Only use this when auto-submit applies (a separated number input whose `minLength` equals
+ * `maxLength`); otherwise the input cannot be submitted, as there is neither a submit button
+ * nor auto-submit.
+ *
* @type {Boolean}
* @public
*/
diff --git a/Input/NumberField.js b/Input/NumberField.js
index e62b834df..479cf3c5f 100644
--- a/Input/NumberField.js
+++ b/Input/NumberField.js
@@ -111,10 +111,11 @@ const NumberFieldBase = kind({
forwardCustomWithPrevent('onBeforeChange', ({value}) => ({value})),
forwardCustom('onChange', (ev) => (ev)),
// Check the length of the new value and return true (pass/proceed) if it is at or above max-length
- ({value: updatedValue}, {maxLength, minLength, numberInputField}) => {
+ ({value: updatedValue}, {maxLength, minLength, noSubmitButton, numberInputField}) => {
const
updatedLength = normalizeValue(updatedValue, maxLength).length,
- autoSubmit = getSeparated(numberInputField, maxLength) && minLength === maxLength;
+ // Auto-submit only when the submit button is omitted for separated equal-length fields
+ autoSubmit = noSubmitButton && getSeparated(numberInputField, maxLength) && minLength === maxLength;
return autoSubmit && updatedLength >= maxLength;
},
forwardCustom('onComplete', (ev) => (ev))
@@ -155,10 +156,10 @@ const NumberFieldBase = kind({
);
}
},
- submitButton: ({buttonSize, css, disabled, invalid, maxLength, minLength, noSubmitButton, onSubmit, value, numberInputField}) => {
+ submitButton: ({buttonSize, css, disabled, invalid, maxLength, minLength, noSubmitButton, onSubmit, value}) => {
const isDisabled = disabled || invalid || (normalizeValue(value, maxLength).toString().length < minLength);
- if (!noSubmitButton && (minLength !== maxLength || !getSeparated(numberInputField, maxLength))) {
+ if (!noSubmitButton) {
return ;
} else {
return null;
diff --git a/Input/tests/Input-specs.js b/Input/tests/Input-specs.js
index 4d74d00f1..b541c4942 100644
--- a/Input/tests/Input-specs.js
+++ b/Input/tests/Input-specs.js
@@ -320,28 +320,112 @@ describe('Input specs', () => {
expect(buttonSubmit).not.toBeNull();
});
- test('should exclude a submit button when separated number input', () => {
+ test('should include a submit button when separated number input', () => {
render(
);
- const buttonSubmit = screen.queryByText('Submit');
+ const buttonSubmit = screen.getByText('Submit');
- expect(buttonSubmit).toBeNull();
+ expect(buttonSubmit).not.toBeNull();
});
- test('should exclude a submit button for explicit separated number input', () => {
+ test('should include a submit button for explicit separated number input', () => {
render(
);
+ const buttonSubmit = screen.getByText('Submit');
+
+ expect(buttonSubmit).not.toBeNull();
+ });
+
+ test('should exclude a submit button for separated number input when noSubmitButton is used', () => {
+ render(
+
+
+
+ );
const buttonSubmit = screen.queryByText('Submit');
expect(buttonSubmit).toBeNull();
});
+ test('should include a submit button for passwordnumber separated input', () => {
+ render(
+
+
+
+ );
+ const buttonSubmit = screen.getByText('Submit');
+
+ expect(buttonSubmit).not.toBeNull();
+ });
+
+ test('should not call onComplete when max length is reached for separated number input with submit button', async () => {
+ jest.useFakeTimers();
+ const spy = jest.fn();
+ const user = userEvent.setup({advanceTimers: jest.advanceTimersByTime});
+ render(
+
+
+
+ );
+ const numberButton = screen.getByText('2');
+
+ await user.click(numberButton);
+
+ act(() => jest.advanceTimersByTime(300));
+
+ expect(spy).not.toHaveBeenCalled();
+
+ jest.useRealTimers();
+ });
+
+ test('should call onComplete when submit button clicked for separated number input with equal min and max length', async () => {
+ jest.useFakeTimers();
+ const spy = jest.fn();
+ const user = userEvent.setup({advanceTimers: jest.advanceTimersByTime});
+ render(
+
+
+
+ );
+ const numberButton = screen.getByText('2');
+ const submitButton = screen.getByText('Submit');
+
+ await user.click(numberButton);
+ await user.click(submitButton);
+
+ act(() => jest.advanceTimersByTime(300));
+
+ expect(spy).toHaveBeenCalled();
+
+ jest.useRealTimers();
+ });
+
+ test('should call onComplete when max length is reached for separated number input with noSubmitButton', async () => {
+ jest.useFakeTimers();
+ const spy = jest.fn();
+ const user = userEvent.setup({advanceTimers: jest.advanceTimersByTime});
+ render(
+
+
+
+ );
+ const numberButton = screen.getByText('2');
+
+ await user.click(numberButton);
+
+ act(() => jest.advanceTimersByTime(300));
+
+ expect(spy).toHaveBeenCalled();
+
+ jest.useRealTimers();
+ });
+
test('should show an invalid tooltip if invalid and message supplied', () => {
render(
diff --git a/tests/ui/specs/Input/Input-specs.js b/tests/ui/specs/Input/Input-specs.js
index fdd2d6fba..a8dbb6216 100644
--- a/tests/ui/specs/Input/Input-specs.js
+++ b/tests/ui/specs/Input/Input-specs.js
@@ -142,6 +142,13 @@ describe('Input test', () => {
await Page.spotlightSelect();
await Page.spotlightSelect();
await Page.spotlightSelect();
+
+ // Submit is required when the button is shown; move focus onto it
+ for (let i = 0; i < 5 && !(await components.input3.submitButton.isFocused()); i++) {
+ await Page.spotlightDown();
+ }
+ expect(await components.input3.submitButton.isFocused()).toBe(true);
+ await Page.spotlightSelect();
await browser.pause(1000);
expect(await components.input3.self.isFocused()).toBe(true);
@@ -198,6 +205,13 @@ describe('Input test', () => {
await Page.spotlightSelect();
await Page.spotlightSelect();
await Page.spotlightSelect();
+
+ // Submit is required when the button is shown; move focus onto it
+ for (let i = 0; i < 5 && !(await components.input4.submitButton.isFocused()); i++) {
+ await Page.spotlightDown();
+ }
+ expect(await components.input4.submitButton.isFocused()).toBe(true);
+ await Page.spotlightSelect();
await browser.pause(1000);
expect(await components.input4.self.isFocused()).toBe(true);
diff --git a/tests/ui/specs/Input/InputPage.js b/tests/ui/specs/Input/InputPage.js
index 61269ebb1..002e1f619 100644
--- a/tests/ui/specs/Input/InputPage.js
+++ b/tests/ui/specs/Input/InputPage.js
@@ -27,6 +27,10 @@ class InputInterface {
return element('.Input_Input_numberCell', browser);
}
+ get submitButton () {
+ return element('.Input_Input_submitButton', browser);
+ }
+
get title () {
return element('.Input_Input_titles', browser);
}