Skip to content

Commit 43795ed

Browse files
fix: allow typing intermediate values below min/max during input (#960)
* fix: validate number on blur * test: add unit test for number field validation * chore: add changeset
1 parent d6c1184 commit 43795ed

File tree

3 files changed

+62
-14
lines changed

3 files changed

+62
-14
lines changed

.changeset/empty-days-film.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
"@shopware-ag/meteor-component-library": minor
3+
---
4+
5+
fix(mt-number-field): allow typing intermediate values below min/max during input

packages/component-library/src/components/form/mt-number-field/mt-number-field.spec.ts

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -352,4 +352,40 @@ describe("mt-number-field", () => {
352352
// ASSERT
353353
expect(screen.getByRole("button", { name: "Decrease" })).not.toHaveFocus();
354354
});
355+
356+
it("allows typing values below the minimum value and validates on blur", async () => {
357+
// ARRANGE
358+
const handler = vi.fn();
359+
360+
render(MtNumberField, {
361+
props: {
362+
modelValue: 10,
363+
min: 8,
364+
max: 80,
365+
"onUpdate:modelValue": handler,
366+
},
367+
});
368+
369+
const input = screen.getByRole("textbox");
370+
371+
// ACT - Type '7' (below minimum value)
372+
await userEvent.clear(input);
373+
await userEvent.type(input, "7");
374+
375+
// ASSERT - '7' should be displayed not fixed to '8'
376+
expect(input).toHaveValue("7");
377+
378+
// ACT - Continue typing '75' to make '775' (invalid value)
379+
await userEvent.type(input, "75");
380+
381+
// ASSERT - "775" should be displayed
382+
expect(input).toHaveValue("775");
383+
384+
// ACT - Blur the field to trigger validation
385+
await userEvent.click(document.body);
386+
387+
// ASSERT - Value '775' is invalid, so it should be clamped to '80'
388+
expect(input).toHaveValue("80");
389+
expect(handler).toHaveBeenCalledWith(80);
390+
});
355391
});

packages/component-library/src/components/form/mt-number-field/mt-number-field.vue

Lines changed: 21 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -314,18 +314,24 @@ export default defineComponent({
314314
315315
onInput(event: Event) {
316316
// @ts-expect-error - target exists
317-
let val = Number.parseFloat(event.target.value);
317+
const inputValue = event.target.value;
318318
319-
if (!Number.isNaN(val)) {
320-
if (this.max && val > this.max) {
321-
val = this.max;
322-
}
323-
if (this.min && val < this.min) {
324-
val = this.min;
325-
}
319+
if (inputValue === "" && this.allowEmpty) {
320+
// @ts-expect-error - defined in parent
321+
this.currentValue = null;
322+
this.$emit("input-change", null);
323+
return;
324+
}
325+
326+
const val = Number.parseFloat(inputValue);
326327
327-
this.computeValue(val.toString());
328+
if (!Number.isNaN(val)) {
329+
this.computeValue(val.toString(), true);
328330
this.$emit("input-change", val);
331+
} else if (inputValue === "" || inputValue === "-" || inputValue === ".") {
332+
// @ts-expect-error - defined in parent
333+
this.currentValue = null;
334+
this.$emit("input-change", null);
329335
}
330336
},
331337
@@ -342,23 +348,24 @@ export default defineComponent({
342348
this.$emit("update:modelValue", this.currentValue);
343349
},
344350
345-
computeValue(stringRepresentation: string) {
351+
computeValue(stringRepresentation: string, skipBoundaries = false) {
346352
const value = this.getNumberFromString(stringRepresentation);
347-
this.currentValue = this.parseValue(value);
353+
this.currentValue = this.parseValue(value, skipBoundaries);
348354
},
349355
350356
// @ts-expect-error - defined in parent
351357
352-
parseValue(value: any) {
358+
parseValue(value: any, skipBoundaries = false) {
353359
if (value === null || Number.isNaN(value) || !Number.isFinite(value)) {
354360
if (this.allowEmpty) {
355361
return null;
356362
}
357363
358-
return this.parseValue(0);
364+
return this.parseValue(0, skipBoundaries);
359365
}
360366
361-
return this.checkForInteger(this.checkBoundaries(value));
367+
const processedValue = skipBoundaries ? value : this.checkBoundaries(value);
368+
return this.checkForInteger(processedValue);
362369
},
363370
364371
checkBoundaries(value: number) {

0 commit comments

Comments
 (0)