Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down
11 changes: 9 additions & 2 deletions Input/Input.js
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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`.
*
Comment thread
dan-ichim-lgp marked this conversation as resolved.
* 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
*/
Expand Down
9 changes: 5 additions & 4 deletions Input/NumberField.js
Original file line number Diff line number Diff line change
Expand Up @@ -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))
Expand Down Expand Up @@ -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 <Button className={css.submitButton} disabled={isDisabled} onClick={onSubmit} size={buttonSize}>{$L('Submit')}</Button>;
} else {
return null;
Expand Down
92 changes: 88 additions & 4 deletions Input/tests/Input-specs.js
Original file line number Diff line number Diff line change
Expand Up @@ -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(
<FloatingLayerController>
<Input type="number" length={4} open />
</FloatingLayerController>
);
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(
<FloatingLayerController>
<Input type="number" length={10} open numberInputField="separated" />
</FloatingLayerController>
);
const buttonSubmit = screen.getByText('Submit');

expect(buttonSubmit).not.toBeNull();
});

test('should exclude a submit button for separated number input when noSubmitButton is used', () => {
render(
<FloatingLayerController>
<Input type="number" length={4} open numberInputField="separated" noSubmitButton />
</FloatingLayerController>
);
const buttonSubmit = screen.queryByText('Submit');

expect(buttonSubmit).toBeNull();
});

test('should include a submit button for passwordnumber separated input', () => {
render(
<FloatingLayerController>
<Input type="passwordnumber" length={4} open />
</FloatingLayerController>
);
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(
<FloatingLayerController>
<Input type="number" length={1} open numberInputField="separated" onComplete={spy} />
</FloatingLayerController>
);
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(
<FloatingLayerController>
<Input type="number" length={1} open numberInputField="separated" onComplete={spy} />
</FloatingLayerController>
);
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(
<FloatingLayerController>
<Input type="number" length={1} open numberInputField="separated" noSubmitButton onComplete={spy} />
</FloatingLayerController>
);
const numberButton = screen.getByText('2');

await user.click(numberButton);

act(() => jest.advanceTimersByTime(300));

expect(spy).toHaveBeenCalled();

jest.useRealTimers();
});

Comment thread
dan-ichim-lgp marked this conversation as resolved.
test('should show an invalid tooltip if invalid and message supplied', () => {
render(
<FloatingLayerController>
Expand Down
14 changes: 14 additions & 0 deletions tests/ui/specs/Input/Input-specs.js
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand Down Expand Up @@ -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);
Expand Down
4 changes: 4 additions & 0 deletions tests/ui/specs/Input/InputPage.js
Original file line number Diff line number Diff line change
Expand Up @@ -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);
}
Expand Down
Loading