feat(input-stepper): align value with step size (#2511)

* feat(input-stepper): add alignToStep property to align value with step size

* Create silver-eyes-count.md

* refactor: make alignTosTep property behavior as default behavior and remove property attribute

* test(input-stepper): handle min value as Infinity and update tests for decrement/increment behavior

---------

Co-authored-by: Manuel Servais <manuel.servais@ing.com>
This commit is contained in:
manuel 2025-05-14 16:46:00 +02:00 committed by GitHub
parent f9f55193c3
commit cf4a8fd1b0
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
4 changed files with 138 additions and 4 deletions

View file

@ -0,0 +1,5 @@
---
"@lion/ui": patch
---
feat(input-stepper): add alignToStep property to align value with step value

View file

@ -111,3 +111,19 @@ export const formatting = () => {
`;
};
```
### Aligning to step
The value is always aligned with the defined step size when using the increase or decrease buttons. The input value will be adjusted to the nearest valid step multiple. For example, with a step of `10` starting from `1`, if the user enters `55` and clicks the increase button, the value will align to `61` instead of `65` (1 > 11 > 21 > ... > 51 > 61).
```html preview-story
<lion-input-stepper
label="Amount of oranges"
min="1"
max="100"
step="10"
name="value"
alignToStep
value="55"
></lion-input-stepper>
```

View file

@ -265,8 +265,19 @@ export class LionInputStepper extends LocalizeMixin(LionInput) {
* @private
*/
__increment() {
const { step, min, max } = this.values;
const newValue = this.currentValue + step;
const { step, max } = this.values;
let { min } = this.values;
if (min === Infinity) {
min = 0;
}
let newValue = this.currentValue + step;
if ((this.currentValue + min) % step !== 0) {
// If the value is not aligned to step, align it to the nearest step
newValue = Math.floor(this.currentValue / step) * step + step + (min % step);
}
if (newValue <= max || max === Infinity) {
this.modelValue = newValue < min && min !== Infinity ? `${min}` : `${newValue}`;
this.__toggleSpinnerButtonsState();
@ -279,8 +290,16 @@ export class LionInputStepper extends LocalizeMixin(LionInput) {
* @private
*/
__decrement() {
const { step, min, max } = this.values;
const newValue = this.currentValue - step;
const { step, max, min } = this.values;
const stepMin = min !== Infinity ? min : 0;
let newValue = this.currentValue - step;
if ((this.currentValue + stepMin) % step !== 0) {
// If the value is not aligned to step, align it to the nearest step
newValue = Math.floor(this.currentValue / step) * step + (stepMin % step);
}
if (newValue >= min || min === Infinity) {
this.modelValue = newValue > max && max !== Infinity ? `${max}` : `${newValue}`;
this.__toggleSpinnerButtonsState();

View file

@ -265,6 +265,100 @@ describe('<lion-input-stepper>', () => {
await nextFrame();
expect(incrementButton?.getAttribute('disabled')).to.equal('true');
});
describe('It align to steps', () => {
it('aligns the value to the nearest step when incrementing', async () => {
let el = await fixture(
html`<lion-input-stepper step="10" min="0" max="100" value="55"></lion-input-stepper>`,
);
let incrementButton = el.querySelector('[slot=suffix]');
incrementButton?.dispatchEvent(new Event('click'));
await el.updateComplete;
expect(el.modelValue).to.equal(60, 'Fail + : (0 > 100 by 10; val 55)');
// min 1
el = await fixture(
html`<lion-input-stepper step="10" min="1" max="100" value="55"></lion-input-stepper>`,
);
incrementButton = el.querySelector('[slot=suffix]');
incrementButton?.dispatchEvent(new Event('click'));
await el.updateComplete;
expect(el.modelValue).to.equal(61, 'Fail + : (1 > 100 by 10; val 55)');
// min 34
el = await fixture(
html`<lion-input-stepper step="10" min="34" max="100" value="55"></lion-input-stepper>`,
);
incrementButton = el.querySelector('[slot=suffix]');
incrementButton?.dispatchEvent(new Event('click'));
await el.updateComplete;
expect(el.modelValue).to.equal(64, 'Fail + : (34 > 100 by 10; val 55)');
// min -23
el = await fixture(
html`<lion-input-stepper step="10" min="-23" max="100" value="55"></lion-input-stepper>`,
);
incrementButton = el.querySelector('[slot=suffix]');
incrementButton?.dispatchEvent(new Event('click'));
await el.updateComplete;
expect(el.modelValue).to.equal(57, 'Fail + : (-23 > 100 by 10; val 55)'); // -23 > -13 > -3 > 7 > ... > 57
// min -23
el = await fixture(
html`<lion-input-stepper step="10" min="-23" max="100" value="-9"></lion-input-stepper>`,
);
incrementButton = el.querySelector('[slot=suffix]');
incrementButton?.dispatchEvent(new Event('click'));
await el.updateComplete;
expect(el.modelValue).to.equal(-3, 'Fail + : (-23 > 100 by 10; val 55)'); // -23 > -13 > -3 > 7
});
it('aligns the value to the nearest step when decrementing', async () => {
let el = await fixture(
html`<lion-input-stepper step="10" min="0" max="100" value="55"></lion-input-stepper>`,
);
let decrementButton = el.querySelector('[slot=prefix]');
decrementButton?.dispatchEvent(new Event('click'));
await el.updateComplete;
expect(el.modelValue).to.equal(50, 'Fail - : (0 > 100 by 10; val 55)');
// min 1
el = await fixture(
html`<lion-input-stepper step="10" min="1" max="100" value="55"></lion-input-stepper>`,
);
decrementButton = el.querySelector('[slot=prefix]');
decrementButton?.dispatchEvent(new Event('click'));
await el.updateComplete;
expect(el.modelValue).to.equal(51, 'Fail - : (1 > 100 by 10; val 55)');
// min 34
el = await fixture(
html`<lion-input-stepper step="10" min="34" max="100" value="55"></lion-input-stepper>`,
);
decrementButton = el.querySelector('[slot=prefix]');
decrementButton?.dispatchEvent(new Event('click'));
await el.updateComplete;
expect(el.modelValue).to.equal(54, 'Fail - : (34 > 100 by 10; val 55)');
// min -23
el = await fixture(
html`<lion-input-stepper step="10" min="-23" max="100" value="55"></lion-input-stepper>`,
);
decrementButton = el.querySelector('[slot=prefix]');
decrementButton?.dispatchEvent(new Event('click'));
await el.updateComplete;
expect(el.modelValue).to.equal(47, 'Fail - : (-23 > 100 by 10; val 55)'); // -23 > -13 > -3 > 7 > ... > 47
// min -23
el = await fixture(
html`<lion-input-stepper step="10" min="-23" max="100" value="-9"></lion-input-stepper>`,
);
decrementButton = el.querySelector('[slot=prefix]');
decrementButton?.dispatchEvent(new Event('click'));
await el.updateComplete;
expect(el.modelValue).to.equal(-13, 'Fail - : (-23 > 100 by 10; val 55)'); // -23 > -13 > -3 > 7
});
});
});
describe('Accessibility', () => {