From fb5eb76d4d50a5233bb3f7fdc0c159ed8ee8cd7a Mon Sep 17 00:00:00 2001
From: ChristianBusshoff <christian.busshoff@mail.schwarz>
Date: Thu, 26 Dec 2024 12:45:24 +0100
Subject: [PATCH 1/8] update Stepper API

---
 .../components/OnyxStepper/OnyxStepper.ct.tsx | 62 ++----------------
 .../components/OnyxStepper/OnyxStepper.vue    | 65 ++++++++++---------
 .../src/components/OnyxStepper/types.ts       | 28 ++++++--
 .../src/composables/useCustomValidity.ts      |  4 +-
 4 files changed, 63 insertions(+), 96 deletions(-)

diff --git a/packages/sit-onyx/src/components/OnyxStepper/OnyxStepper.ct.tsx b/packages/sit-onyx/src/components/OnyxStepper/OnyxStepper.ct.tsx
index afed0a705c..eb1fa372d9 100644
--- a/packages/sit-onyx/src/components/OnyxStepper/OnyxStepper.ct.tsx
+++ b/packages/sit-onyx/src/components/OnyxStepper/OnyxStepper.ct.tsx
@@ -336,53 +336,6 @@ test("should increment/decrement value by one on counter button click", async ({
   await expect(input).toHaveValue("0");
 });
 
-test("should increment/decrement value by step on counter button click", async ({
-  mount,
-  makeAxeBuilder,
-}) => {
-  // ARRANGE
-  const on = {
-    "update:modelValue": (newValue) => {
-      component.update({
-        props: {
-          modelValue: newValue,
-        },
-        on,
-      });
-    },
-  };
-
-  const component = await mount(OnyxStepper, {
-    props: {
-      label: "Test label",
-      style: "width: 12rem;",
-      stepSize: 2,
-    },
-    on,
-  });
-
-  const input = component.getByLabel("Test label");
-  const addButton = component.getByLabel("Increment");
-  const substractButton = component.getByLabel("Decrement by 2");
-
-  // ACT
-  const accessibilityScanResults = await makeAxeBuilder().analyze();
-
-  // ASSERT
-  expect(accessibilityScanResults.violations).toEqual([]);
-  await expect(component.getByLabel("Test label")).toBeAttached();
-
-  await input.click();
-  await input.fill("0");
-  await expect(input).toHaveValue("0");
-
-  await addButton.click();
-  await expect(input).toHaveValue("2");
-
-  await substractButton.click();
-  await expect(input).toHaveValue("0");
-});
-
 test("should not allow entering value over the max value that has been set", async ({
   mount,
   makeAxeBuilder,
@@ -476,7 +429,7 @@ test("should not allow entering value lower the min value that has been set", as
   await expect(substractButton).toBeDisabled();
 });
 
-test("Should display the same number of decimal places as the smallest possible step", async ({
+test("Should correctly display decimal places according to the defined precision", async ({
   mount,
   makeAxeBuilder,
 }) => {
@@ -496,7 +449,7 @@ test("Should display the same number of decimal places as the smallest possible
     props: {
       label: "Test label",
       style: "width: 12rem;",
-      precision: 0.01,
+      precision: 2,
     },
     on,
   });
@@ -514,7 +467,7 @@ test("Should display the same number of decimal places as the smallest possible
   await expect(input).toHaveValue("1.00");
 });
 
-test("Should display an error if the value is not a multiple of the precision", async ({
+test("Should display an error if the value is not a multiple of validStepSize", async ({
   page,
   mount,
   makeAxeBuilder,
@@ -536,7 +489,7 @@ test("Should display an error if the value is not a multiple of the precision",
       label: "Test label",
       style: "width: 12rem;",
       modelValue: 1,
-      precision: 0.5,
+      validStepSize: 0.5,
     },
     on,
   });
@@ -554,7 +507,6 @@ test("Should display an error if the value is not a multiple of the precision",
   await page.keyboard.press("Enter");
 
   await expect(errorMessage).toBeHidden();
-  await page.keyboard.press("Enter");
 
   await input.fill("3.6");
   await page.keyboard.press("Enter");
@@ -582,7 +534,7 @@ test("Should revert to the last valid input if the current input is invalid in s
     props: {
       label: "Test label",
       style: "width: 12rem;",
-      precision: 0.5,
+      validStepSize: 0.5,
       stripStep: true,
     },
     on,
@@ -592,9 +544,9 @@ test("Should revert to the last valid input if the current input is invalid in s
 
   await input.fill("1");
   await page.keyboard.press("Enter");
-  await expect(input).toHaveValue("1.0");
+  await expect(input).toHaveValue("1");
   await page.keyboard.press("Enter");
   await input.fill("1.6");
   await page.keyboard.press("Enter");
-  await expect(input).toHaveValue("1.0");
+  await expect(input).toHaveValue("1");
 });
diff --git a/packages/sit-onyx/src/components/OnyxStepper/OnyxStepper.vue b/packages/sit-onyx/src/components/OnyxStepper/OnyxStepper.vue
index 5c2855be10..ed3c08d5ba 100644
--- a/packages/sit-onyx/src/components/OnyxStepper/OnyxStepper.vue
+++ b/packages/sit-onyx/src/components/OnyxStepper/OnyxStepper.vue
@@ -15,7 +15,9 @@ import OnyxLoadingIndicator from "../OnyxLoadingIndicator/OnyxLoadingIndicator.v
 import OnyxSkeleton from "../OnyxSkeleton/OnyxSkeleton.vue";
 import type { OnyxStepperProps } from "./types";
 const props = withDefaults(defineProps<OnyxStepperProps>(), {
-  precision: 1,
+  precision: undefined,
+  stepSize: 1,
+  validStepSize: undefined,
   stripStep: false,
   readonly: false,
   loading: false,
@@ -50,47 +52,48 @@ const modelValue = defineModel<number>();
  */
 const inputValue = ref<string>();
 
-const decimalPlaces = computed(() => {
-  const precision = props.precision;
-  const precisionStr = precision.toString();
-  if (precisionStr.includes(".")) {
-    return precisionStr.split(".")[1].length;
-  }
-  return -Math.floor(Math.log10(precision));
-});
-
 watch(
   modelValue,
-  () => (inputValue.value = roundToPrecision(modelValue.value, decimalPlaces.value)),
+  () => {
+    if (props.precision) {
+      inputValue.value = roundToPrecision(modelValue.value, props.precision);
+    } else {
+      inputValue.value = modelValue.value?.toString();
+    }
+  },
   {
     immediate: true,
   },
 );
 
-// * stepSize must be precision or bigger
-// * use precision as fallback
-const determinedStepSize = computed(() =>
-  Math.max(props.stepSize ?? props.precision, props.precision),
-);
-
 const handleClick = (direction: "stepUp" | "stepDown") => {
   if (!inputRef.value) return;
   wasTouched.value = true;
   const currentValue = modelValue.value || 0;
-  const stepValue = (direction === "stepUp" ? 1 : -1) * determinedStepSize.value;
+  const stepValue = (direction === "stepUp" ? 1 : -1) * props.stepSize;
   const newValue = currentValue + stepValue;
-  const roundedValue = Math.round(newValue / props.precision) * props.precision;
-
-  modelValue.value = applyLimits(roundedValue, props.min, props.max);
+  // correcting invalid inputs to the next valid number
+  const roundedValue = props.validStepSize
+    ? Math.round((newValue - (props.min || 0)) / props.validStepSize) * props.validStepSize +
+      (props.min || 0)
+    : newValue;
+  const precisionAdjustedValue = props.precision
+    ? parseFloat(roundToPrecision(roundedValue, props.precision))
+    : roundedValue;
+  modelValue.value = applyLimits(precisionAdjustedValue, props.min, props.max);
 };
 
 const handleChange = () => {
   if (!inputRef.value) return;
   wasTouched.value = true;
   const newValue = parseFloat(inputValue.value ?? "");
-  const rounded = parseFloat(roundToPrecision(newValue, decimalPlaces.value));
+  const rounded = props.precision
+    ? parseFloat(roundToPrecision(newValue, props.precision))
+    : newValue;
   // reset input
-  inputValue.value = roundToPrecision(newValue, decimalPlaces.value);
+  inputValue.value = props.precision
+    ? roundToPrecision(newValue, props.precision)
+    : newValue.toString();
 
   if (!newValue || isNaN(newValue)) {
     modelValue.value = undefined;
@@ -99,22 +102,20 @@ const handleChange = () => {
 
   if (
     props.stripStep &&
-    (!isDivisible(newValue, props.precision) ||
+    ((props.validStepSize && !isDivisible(newValue, props.validStepSize)) ||
       (props.min !== undefined && props.min > newValue) ||
       (props.max !== undefined && props.max < newValue))
   ) {
-    inputValue.value = roundToPrecision(modelValue.value, decimalPlaces.value);
+    inputValue.value = props.precision
+      ? roundToPrecision(modelValue.value, props.precision)
+      : modelValue.value?.toString();
     return;
   }
   modelValue.value = rounded;
 };
 
-const incrementLabel = computed(() =>
-  t.value("stepper.increment", { stepSize: determinedStepSize.value }),
-);
-const decrementLabel = computed(() =>
-  t.value("stepper.decrement", { stepSize: determinedStepSize.value }),
-);
+const incrementLabel = computed(() => t.value("stepper.increment", { stepSize: props.stepSize }));
+const decrementLabel = computed(() => t.value("stepper.decrement", { stepSize: props.stepSize }));
 </script>
 
 <template>
@@ -163,7 +164,7 @@ const decrementLabel = computed(() =>
           :placeholder="props.placeholder"
           :readonly="props.readonly"
           :required="props.required"
-          :step="props.precision"
+          :step="props.validStepSize ?? 'any'"
           :title="props.hideLabel ? props.label : undefined"
           @change="handleChange"
           @keydown.up.prevent="handleClick('stepUp')"
diff --git a/packages/sit-onyx/src/components/OnyxStepper/types.ts b/packages/sit-onyx/src/components/OnyxStepper/types.ts
index b95a939b57..f635df43e8 100644
--- a/packages/sit-onyx/src/components/OnyxStepper/types.ts
+++ b/packages/sit-onyx/src/components/OnyxStepper/types.ts
@@ -33,18 +33,32 @@ export type OnyxStepperProps = FormInjectedProps &
     step?: number; // step-mismatch + step-increment
 
     /**
-     * The smallest allowed value and rounded precision
+     * Number of decimal places to show. Can also be negative. Value will be rounded if needed
+     * to match the specified precision.
+     *
+     * @example For `precision=2` with `modelValue=5`, "5.00" will be displayed.
+     * @example For `precision=-1` with `modelValue=60`, "100" will be displayed.
+     * @default undefined
      */
-    precision?: number; // step-mismatch => uses :step="props.precision" for the validation
+    precision?: number;
+
     /**
-     * The increment number
-     * @default precision is the default stepSize
+     * Defines how much the value is adjusted when clicking the +/- button.
+     *
+     * @default 1
      */
-    stepSize?: number; //  step-increment => number which is used for increment/decrement
-
+    stepSize?: number;
     /**
-     * Ensure no wrong number can be inputed
+     * Defines step size f0r valid/allowed values. Can be independent of the `stepSize` property.
+     * Uses the `min` property as base so if defining min=3 with validStepSize=2, only odd numbers will
+     *
+     * @example For `validStepSize` 0.01, only multiples of 0.01 are valid (useful for currencies)
+     * @example For `stepSize=4` `validStepSize=2`, only even numbers are valid and the user can adjust the value by 4 when clicking the +/- button.
+     * @example For `min=3` and `validStepSize=2`, only odd numbers will be valid
+     * @default undefined
      */
+    validStepSize?: number;
+
     stripStep?: boolean;
 
     /**
diff --git a/packages/sit-onyx/src/composables/useCustomValidity.ts b/packages/sit-onyx/src/composables/useCustomValidity.ts
index 01309d410f..e3094747e4 100644
--- a/packages/sit-onyx/src/composables/useCustomValidity.ts
+++ b/packages/sit-onyx/src/composables/useCustomValidity.ts
@@ -28,7 +28,7 @@ export type UseCustomValidityOptions = {
     minlength?: number;
     min?: DateValue;
     max?: DateValue;
-    precision?: number;
+    validStepSize?: number;
   } & Pick<BaseSelectOption, "hideLabel" | "label">;
   /**
    * Component emit as defined with `const emit = defineEmits()`
@@ -205,7 +205,7 @@ export const useCustomValidity = (options: UseCustomValidityOptions) => {
       maxLength: options.props.maxlength,
       min: formatMinMax(locale.value, options.props.type, options.props.min),
       max: formatMinMax(locale.value, options.props.type, options.props.max),
-      step: options.props.precision,
+      step: options.props.validStepSize,
     };
 
     return {

From f5b11693985282c19bd7e3e796e46e23c1573536 Mon Sep 17 00:00:00 2001
From: ChristianBusshoff <christian.busshoff@mail.schwarz>
Date: Fri, 27 Dec 2024 11:01:54 +0100
Subject: [PATCH 2/8] fix: type discriptions

---
 .../sit-onyx/src/components/OnyxStepper/types.ts    | 13 +++++++------
 1 file changed, 7 insertions(+), 6 deletions(-)

diff --git a/packages/sit-onyx/src/components/OnyxStepper/types.ts b/packages/sit-onyx/src/components/OnyxStepper/types.ts
index f635df43e8..e515dd8fd6 100644
--- a/packages/sit-onyx/src/components/OnyxStepper/types.ts
+++ b/packages/sit-onyx/src/components/OnyxStepper/types.ts
@@ -24,9 +24,8 @@ export type OnyxStepperProps = FormInjectedProps &
      */
     max?: number;
     /**
-     * Incremental step.
-     */
-    /**
+     * Ensure no wrong number can be inputed
+     */ /**
      * Incremental step.
      * @deprecated
      */
@@ -37,7 +36,7 @@ export type OnyxStepperProps = FormInjectedProps &
      * to match the specified precision.
      *
      * @example For `precision=2` with `modelValue=5`, "5.00" will be displayed.
-     * @example For `precision=-1` with `modelValue=60`, "100" will be displayed.
+     * @example For `precision=-2` with `modelValue=60`, "100" will be displayed.
      * @default undefined
      */
     precision?: number;
@@ -49,7 +48,7 @@ export type OnyxStepperProps = FormInjectedProps &
      */
     stepSize?: number;
     /**
-     * Defines step size f0r valid/allowed values. Can be independent of the `stepSize` property.
+     * Defines step size for valid/allowed values. Can be independent of the `stepSize` property.
      * Uses the `min` property as base so if defining min=3 with validStepSize=2, only odd numbers will
      *
      * @example For `validStepSize` 0.01, only multiples of 0.01 are valid (useful for currencies)
@@ -58,7 +57,9 @@ export type OnyxStepperProps = FormInjectedProps &
      * @default undefined
      */
     validStepSize?: number;
-
+    /**
+     * Ensure no wrong number can be inputed
+     */
     stripStep?: boolean;
 
     /**

From 7a039eeac636a5e3e074f50200a2a4a518937837 Mon Sep 17 00:00:00 2001
From: ChristianBusshoff <christian.busshoff@mail.schwarz>
Date: Fri, 27 Dec 2024 16:54:19 +0100
Subject: [PATCH 3/8] change

---
 packages/sit-onyx/src/components/OnyxStepper/types.ts | 2 --
 1 file changed, 2 deletions(-)

diff --git a/packages/sit-onyx/src/components/OnyxStepper/types.ts b/packages/sit-onyx/src/components/OnyxStepper/types.ts
index e515dd8fd6..635c274f66 100644
--- a/packages/sit-onyx/src/components/OnyxStepper/types.ts
+++ b/packages/sit-onyx/src/components/OnyxStepper/types.ts
@@ -24,8 +24,6 @@ export type OnyxStepperProps = FormInjectedProps &
      */
     max?: number;
     /**
-     * Ensure no wrong number can be inputed
-     */ /**
      * Incremental step.
      * @deprecated
      */

From 939ea387b306f5c73cd9de87b25b296af7fd201c Mon Sep 17 00:00:00 2001
From: ChristianBusshoff <christian.busshoff@mail.schwarz>
Date: Thu, 2 Jan 2025 08:40:28 +0100
Subject: [PATCH 4/8] docs(changeset): feat(OnyxStepper): update StepperAPI

---
 .changeset/unlucky-mice-ring.md | 5 +++++
 1 file changed, 5 insertions(+)
 create mode 100644 .changeset/unlucky-mice-ring.md

diff --git a/.changeset/unlucky-mice-ring.md b/.changeset/unlucky-mice-ring.md
new file mode 100644
index 0000000000..7b2b03d48d
--- /dev/null
+++ b/.changeset/unlucky-mice-ring.md
@@ -0,0 +1,5 @@
+---
+"sit-onyx": minor
+---
+
+feat(OnyxStepper): update StepperAPI

From 4d2220f7291fb1aa1b33daa8987b2c0877db8a60 Mon Sep 17 00:00:00 2001
From: ChristianBusshoff <christian.busshoff@mail.schwarz>
Date: Thu, 2 Jan 2025 08:43:01 +0100
Subject: [PATCH 5/8] add changeset description

---
 .changeset/unlucky-mice-ring.md | 4 ++++
 1 file changed, 4 insertions(+)

diff --git a/.changeset/unlucky-mice-ring.md b/.changeset/unlucky-mice-ring.md
index 7b2b03d48d..97b6029146 100644
--- a/.changeset/unlucky-mice-ring.md
+++ b/.changeset/unlucky-mice-ring.md
@@ -3,3 +3,7 @@
 ---
 
 feat(OnyxStepper): update StepperAPI
+
+- stepSize: Defines how much the value is adjusted when clicking the +/- button.
+- precision: Number of decimal places to show.
+- validStepSize: Defines step size fir valid/allowed values.

From e44608c82e56950b72c86e8f9b5b988097988853 Mon Sep 17 00:00:00 2001
From: Lars Rickert <lars.rickert@mail.schwarz>
Date: Thu, 2 Jan 2025 14:45:36 +0100
Subject: [PATCH 6/8] implement PR review feedback for #2416 (#2428)

PR review feedback/changes for #2416

Changes:
- update changeset
- update tests
- remove unused `step` and `stripStep` property as well as code to no
longer modify the user entered value when `validStepSize` is set
---
 .changeset/unlucky-mice-ring.md               |  11 +-
 .../src/components/form-demo/FormDemo.vue     |   9 +-
 .../components/OnyxStepper/OnyxStepper.ct.tsx | 175 +++++++-----------
 .../OnyxStepper/OnyxStepper.stories.ts        |  11 ++
 .../components/OnyxStepper/OnyxStepper.vue    |  78 +++-----
 .../src/components/OnyxStepper/types.ts       |  29 +--
 .../EditGridElementDialog.vue                 |   2 +
 packages/sit-onyx/src/utils/numbers.spec.ts   |  26 +--
 packages/sit-onyx/src/utils/numbers.ts        |  23 +--
 9 files changed, 120 insertions(+), 244 deletions(-)

diff --git a/.changeset/unlucky-mice-ring.md b/.changeset/unlucky-mice-ring.md
index 97b6029146..74543e16b5 100644
--- a/.changeset/unlucky-mice-ring.md
+++ b/.changeset/unlucky-mice-ring.md
@@ -1,9 +1,10 @@
 ---
-"sit-onyx": minor
+"sit-onyx": major
 ---
 
-feat(OnyxStepper): update StepperAPI
+feat(OnyxStepper): update OnyxStepper API
 
-- stepSize: Defines how much the value is adjusted when clicking the +/- button.
-- precision: Number of decimal places to show.
-- validStepSize: Defines step size fir valid/allowed values.
+- remove property `step`, use `stepSize` instead
+- remove property `stripStep`, use `validStepSize` instead. User inputs will no longer be manipulated, instead an error will be shown
+- changed logic of `precision` property. Now determined numbers of decimal places to show. Is no longer the default value of `stepSize` property.
+- fix bug that decimal value is not displayed correctly when `precision` is not set
diff --git a/apps/demo-app/src/components/form-demo/FormDemo.vue b/apps/demo-app/src/components/form-demo/FormDemo.vue
index da8fb78c5b..6741dea560 100644
--- a/apps/demo-app/src/components/form-demo/FormDemo.vue
+++ b/apps/demo-app/src/components/form-demo/FormDemo.vue
@@ -127,7 +127,7 @@ const radioOptions: RadioButtonOption[] = [
       :minlength="5"
       required
     />
-    <OnyxStepper v-model="formState.defaultStepper" class="onyx-grid-span-4" label="Delault" />
+    <OnyxStepper v-model="formState.defaultStepper" class="onyx-grid-span-4" label="Default" />
     <OnyxStepper
       v-model="formState.requiredStepper"
       class="onyx-grid-span-4"
@@ -142,13 +142,6 @@ const radioOptions: RadioButtonOption[] = [
       :max="20"
     />
 
-    <OnyxStepper
-      v-model="formState.stripStepStepper"
-      class="onyx-grid-span-4"
-      label="Strip Step"
-      strip-step
-    />
-
     <OnyxSwitch v-model="formState.switch" class="onyx-grid-span-4" label="Switch" required />
 
     <OnyxCheckboxGroup
diff --git a/packages/sit-onyx/src/components/OnyxStepper/OnyxStepper.ct.tsx b/packages/sit-onyx/src/components/OnyxStepper/OnyxStepper.ct.tsx
index eb1fa372d9..49a2a652f7 100644
--- a/packages/sit-onyx/src/components/OnyxStepper/OnyxStepper.ct.tsx
+++ b/packages/sit-onyx/src/components/OnyxStepper/OnyxStepper.ct.tsx
@@ -236,9 +236,9 @@ test.describe("Screenshot tests", () => {
   });
 });
 
-test("should emit events", async ({ mount, makeAxeBuilder }) => {
+test("should emit events", async ({ mount }) => {
   const events = {
-    updateModelValue: [] as string[],
+    updateModelValue: [] as number[],
   };
 
   // ARRANGE
@@ -254,12 +254,6 @@ test("should emit events", async ({ mount, makeAxeBuilder }) => {
     updateModelValue: [],
   });
 
-  // ACT
-  const accessibilityScanResults = await makeAxeBuilder().analyze();
-
-  // ASSERT
-  expect(accessibilityScanResults.violations).toEqual([]);
-
   const inputElement = component.getByLabel("Label");
 
   // ACT
@@ -290,13 +284,10 @@ test("should have aria-label if label is hidden", async ({ mount, makeAxeBuilder
   await expect(component.getByLabel("Test label")).toBeAttached();
 });
 
-test("should increment/decrement value by one on counter button click", async ({
-  mount,
-  makeAxeBuilder,
-}) => {
+test("should increment/decrement value on counter button click", async ({ mount }) => {
   // ARRANGE
   const on = {
-    "update:modelValue": (newValue) => {
+    "update:modelValue": (newValue?: number) => {
       component.update({
         props: {
           modelValue: newValue,
@@ -310,39 +301,29 @@ test("should increment/decrement value by one on counter button click", async ({
     props: {
       label: "Test label",
       style: "width: 12rem;",
+      stepSize: 2,
     },
     on,
   });
 
   const input = component.getByLabel("Test label");
-  const incrementButton = component.getByLabel("Increment by 1");
-  const decrementButton = component.getByLabel("Decrement by 1");
-
-  // ACT
-  const accessibilityScanResults = await makeAxeBuilder().analyze();
+  const incrementButton = component.getByLabel("Increment by 2");
+  const decrementButton = component.getByLabel("Decrement by 2");
 
   // ASSERT
-  expect(accessibilityScanResults.violations).toEqual([]);
-  await expect(component.getByLabel("Test label")).toBeAttached();
-
-  await input.click();
-  await input.fill("0");
-  await expect(input).toHaveValue("0");
+  await expect(input).toHaveValue("");
 
   await incrementButton.click();
-  await expect(input).toHaveValue("1");
+  await expect(input).toHaveValue("2");
 
   await decrementButton.click();
   await expect(input).toHaveValue("0");
 });
 
-test("should not allow entering value over the max value that has been set", async ({
-  mount,
-  makeAxeBuilder,
-}) => {
+test("should not allow entering value over the max value that has been set", async ({ mount }) => {
   // ARRANGE
   const on = {
-    "update:modelValue": (newValue) => {
+    "update:modelValue": (newValue?: number) => {
       component.update({
         props: {
           modelValue: newValue,
@@ -356,41 +337,28 @@ test("should not allow entering value over the max value that has been set", asy
     props: {
       label: "Test label",
       style: "width: 12rem;",
-      max: 2,
+      max: 3,
+      stepSize: 2,
     },
     on,
   });
 
   const input = component.getByLabel("Test label");
-  const addButton = component.getByLabel("Increment by 1");
-
-  // ACT
-  const accessibilityScanResults = await makeAxeBuilder().analyze();
+  const addButton = component.getByLabel("Increment by 2");
 
   // ASSERT
-  expect(accessibilityScanResults.violations).toEqual([]);
-  await expect(component.getByLabel("Test label")).toBeAttached();
-
-  await input.click();
-  await input.fill("0");
-  await expect(input).toHaveValue("0");
-
-  await addButton.click();
-  await expect(input).toHaveValue("1");
-
   await addButton.click();
   await expect(input).toHaveValue("2");
 
+  await addButton.click();
+  await expect(input).toHaveValue("3");
   await expect(addButton).toBeDisabled();
 });
 
-test("should not allow entering value lower the min value that has been set", async ({
-  mount,
-  makeAxeBuilder,
-}) => {
+test("should not allow entering value lower the min value that has been set", async ({ mount }) => {
   // ARRANGE
   const on = {
-    "update:modelValue": (newValue) => {
+    "update:modelValue": (newValue?: number) => {
       component.update({
         props: {
           modelValue: newValue,
@@ -405,37 +373,33 @@ test("should not allow entering value lower the min value that has been set", as
       label: "Test label",
       style: "width: 12rem;",
       min: 2,
-      modelValue: 4,
+      stepSize: 2,
+      modelValue: 5,
     },
     on,
   });
 
   const input = component.getByLabel("Test label");
-  const substractButton = component.getByLabel("Decrement by 1");
-
-  // ACT
-  const accessibilityScanResults = await makeAxeBuilder().analyze();
+  const substractButton = component.getByLabel("Decrement by 2");
 
   // ASSERT
-  expect(accessibilityScanResults.violations).toEqual([]);
-  await expect(component.getByLabel("Test label")).toBeAttached();
-
   await substractButton.click();
   await expect(input).toHaveValue("3");
 
   await substractButton.click();
   await expect(input).toHaveValue("2");
-
   await expect(substractButton).toBeDisabled();
 });
 
 test("Should correctly display decimal places according to the defined precision", async ({
   mount,
-  makeAxeBuilder,
 }) => {
+  const modelValueUpdates = [] as (number | undefined)[];
+
   // ARRANGE
   const on = {
-    "update:modelValue": (newValue) => {
+    "update:modelValue": (newValue?: number) => {
+      modelValueUpdates.push(newValue);
       component.update({
         props: {
           modelValue: newValue,
@@ -450,31 +414,55 @@ test("Should correctly display decimal places according to the defined precision
       label: "Test label",
       style: "width: 12rem;",
       precision: 2,
+      modelValue: 1,
     },
     on,
   });
 
-  const input = component.locator("input");
+  const input = component.getByLabel("Test label");
+
+  // ASSERT
+  await expect(input).toHaveValue("1.00");
 
   // ACT
-  const accessibilityScanResults = await makeAxeBuilder().analyze();
+  await input.fill("3.1");
+  await input.blur();
 
   // ASSERT
-  expect(accessibilityScanResults.violations).toEqual([]);
+  await expect(input).toHaveValue("3.10");
+  expect(modelValueUpdates).toStrictEqual([3.1]);
 
-  await input.fill("1");
-  await input.dispatchEvent("change");
-  await expect(input).toHaveValue("1.00");
+  // ACT
+  await input.fill("3.106");
+  await input.blur();
+
+  // ASSERT
+  await expect(input).toHaveValue("3.11");
+  expect(modelValueUpdates).toStrictEqual([3.1, 3.11]);
+
+  // ACT
+  await component.update({ props: { precision: 1 }, on });
+
+  // ASSERT
+  await expect(input).toHaveValue("3.1");
+
+  // ACT
+  await component.update({ props: { precision: -1 }, on });
+  await input.fill("6");
+  await input.blur();
+
+  // ASSERT
+  await expect(input).toHaveValue("10");
+  expect(modelValueUpdates).toStrictEqual([3.1, 3.11, 10]);
 });
 
 test("Should display an error if the value is not a multiple of validStepSize", async ({
   page,
   mount,
-  makeAxeBuilder,
 }) => {
   // ARRANGE
   const on = {
-    "update:modelValue": (newValue: number) => {
+    "update:modelValue": (newValue?: number) => {
       component.update({
         props: {
           modelValue: newValue,
@@ -498,55 +486,20 @@ test("Should display an error if the value is not a multiple of validStepSize",
   const errorMessage = component.locator(".onyx-form-element__error-message");
 
   // ACT
-  const accessibilityScanResults = await makeAxeBuilder().analyze();
-
-  // ASSERT
-  expect(accessibilityScanResults.violations).toEqual([]);
-
   await input.fill("1");
   await page.keyboard.press("Enter");
 
+  // ASSERT
   await expect(errorMessage).toBeHidden();
 
+  // ACT
   await input.fill("3.6");
   await page.keyboard.press("Enter");
 
+  // ASSERT
   await expect(errorMessage).toBeVisible();
-});
-
-test("Should revert to the last valid input if the current input is invalid in stripStep mode", async ({
-  page,
-  mount,
-}) => {
-  // ARRANGE
-  const on = {
-    "update:modelValue": (newValue: number) => {
-      component.update({
-        props: {
-          modelValue: newValue,
-        },
-        on,
-      });
-    },
-  };
-
-  const component = await mount(OnyxStepper, {
-    props: {
-      label: "Test label",
-      style: "width: 12rem;",
-      validStepSize: 0.5,
-      stripStep: true,
-    },
-    on,
-  });
-
-  const input = component.locator("input");
-
-  await input.fill("1");
-  await page.keyboard.press("Enter");
-  await expect(input).toHaveValue("1");
-  await page.keyboard.press("Enter");
-  await input.fill("1.6");
-  await page.keyboard.press("Enter");
-  await expect(input).toHaveValue("1");
+  await expect(errorMessage).toContainText("Invalid number");
+  await expect(errorMessage).toContainText(
+    "Please enter a valid number, that is a multiple of 0.5.",
+  );
 });
diff --git a/packages/sit-onyx/src/components/OnyxStepper/OnyxStepper.stories.ts b/packages/sit-onyx/src/components/OnyxStepper/OnyxStepper.stories.ts
index 815d97a4e1..1584deffce 100644
--- a/packages/sit-onyx/src/components/OnyxStepper/OnyxStepper.stories.ts
+++ b/packages/sit-onyx/src/components/OnyxStepper/OnyxStepper.stories.ts
@@ -28,6 +28,17 @@ export const Default = {
   },
 } satisfies Story;
 
+/**
+ * This example shows a stepper with precision two always show two decimal places.
+ */
+export const Precision = {
+  args: {
+    label: "Currency",
+    modelValue: 5,
+    precision: 2,
+  },
+} satisfies Story;
+
 /**
  * This example shows the stepper with a placeholder.
  */
diff --git a/packages/sit-onyx/src/components/OnyxStepper/OnyxStepper.vue b/packages/sit-onyx/src/components/OnyxStepper/OnyxStepper.vue
index ed3c08d5ba..cdb11020b0 100644
--- a/packages/sit-onyx/src/components/OnyxStepper/OnyxStepper.vue
+++ b/packages/sit-onyx/src/components/OnyxStepper/OnyxStepper.vue
@@ -1,38 +1,38 @@
 <script lang="ts" setup>
 import minus from "@sit-onyx/icons/minus.svg?raw";
 import plus from "@sit-onyx/icons/plus.svg?raw";
-import { computed, ref, watch } from "vue";
+import { computed, ref, watchEffect } from "vue";
 import { useDensity } from "../../composables/density";
 import { getFormMessages, useCustomValidity } from "../../composables/useCustomValidity";
 import { useErrorClass } from "../../composables/useErrorClass";
 import { SKELETON_INJECTED_SYMBOL, useSkeletonContext } from "../../composables/useSkeletonState";
 import { injectI18n } from "../../i18n";
-import { applyLimits, isDivisible, roundToPrecision } from "../../utils/numbers";
+import { applyLimits, roundToPrecision } from "../../utils/numbers";
 import { FORM_INJECTED_SYMBOL, useFormContext } from "../OnyxForm/OnyxForm.core";
 import OnyxFormElement from "../OnyxFormElement/OnyxFormElement.vue";
 import OnyxIcon from "../OnyxIcon/OnyxIcon.vue";
 import OnyxLoadingIndicator from "../OnyxLoadingIndicator/OnyxLoadingIndicator.vue";
 import OnyxSkeleton from "../OnyxSkeleton/OnyxSkeleton.vue";
 import type { OnyxStepperProps } from "./types";
+
 const props = withDefaults(defineProps<OnyxStepperProps>(), {
-  precision: undefined,
   stepSize: 1,
-  validStepSize: undefined,
-  stripStep: false,
   readonly: false,
   loading: false,
   skeleton: SKELETON_INJECTED_SYMBOL,
   disabled: FORM_INJECTED_SYMBOL,
   showError: FORM_INJECTED_SYMBOL,
 });
-const { t } = injectI18n();
-const inputRef = ref<HTMLInputElement>();
+
 const emit = defineEmits<{
   /**
    * Emitted when the validity state of the input changes.
    */
   validityChange: [validity: ValidityState];
 }>();
+
+const { t } = injectI18n();
+
 const { disabled, showError } = useFormContext(props);
 const skeleton = useSkeletonContext(props);
 const errorClass = useErrorClass(showError);
@@ -40,78 +40,51 @@ const { densityClass } = useDensity(props);
 const { vCustomValidity, errorMessages } = useCustomValidity({ props, emit });
 const successMessages = computed(() => getFormMessages(props.success));
 const messages = computed(() => getFormMessages(props.message));
+
 /**
  * Used to detect user interaction to simulate the behavior of :user-invalid for the native input
  * because the native browser :user-invalid does not trigger when the value is changed via Arrow up/down or increase/decrease buttons
  */
 const wasTouched = ref(false);
 const modelValue = defineModel<number>();
+
 /**
  * Used for syncing the actual input value.
  * We use string to be able to control the number of decimal places.
  */
 const inputValue = ref<string>();
 
-watch(
-  modelValue,
-  () => {
-    if (props.precision) {
-      inputValue.value = roundToPrecision(modelValue.value, props.precision);
+const getFormattedValue = computed(() => {
+  return (value?: number) => {
+    if (props.precision !== undefined && value !== undefined) {
+      return roundToPrecision(value, props.precision);
     } else {
-      inputValue.value = modelValue.value?.toString();
+      return value?.toString() ?? "";
     }
-  },
-  {
-    immediate: true,
-  },
-);
+  };
+});
+
+watchEffect(() => {
+  inputValue.value = getFormattedValue.value(modelValue.value);
+});
 
 const handleClick = (direction: "stepUp" | "stepDown") => {
-  if (!inputRef.value) return;
   wasTouched.value = true;
   const currentValue = modelValue.value || 0;
   const stepValue = (direction === "stepUp" ? 1 : -1) * props.stepSize;
-  const newValue = currentValue + stepValue;
-  // correcting invalid inputs to the next valid number
-  const roundedValue = props.validStepSize
-    ? Math.round((newValue - (props.min || 0)) / props.validStepSize) * props.validStepSize +
-      (props.min || 0)
-    : newValue;
-  const precisionAdjustedValue = props.precision
-    ? parseFloat(roundToPrecision(roundedValue, props.precision))
-    : roundedValue;
-  modelValue.value = applyLimits(precisionAdjustedValue, props.min, props.max);
+  const newValue = parseFloat(getFormattedValue.value(currentValue + stepValue));
+  modelValue.value = applyLimits(newValue, props.min, props.max);
 };
 
 const handleChange = () => {
-  if (!inputRef.value) return;
   wasTouched.value = true;
-  const newValue = parseFloat(inputValue.value ?? "");
-  const rounded = props.precision
-    ? parseFloat(roundToPrecision(newValue, props.precision))
-    : newValue;
-  // reset input
-  inputValue.value = props.precision
-    ? roundToPrecision(newValue, props.precision)
-    : newValue.toString();
-
-  if (!newValue || isNaN(newValue)) {
+  if (!inputValue.value) {
     modelValue.value = undefined;
     return;
   }
 
-  if (
-    props.stripStep &&
-    ((props.validStepSize && !isDivisible(newValue, props.validStepSize)) ||
-      (props.min !== undefined && props.min > newValue) ||
-      (props.max !== undefined && props.max < newValue))
-  ) {
-    inputValue.value = props.precision
-      ? roundToPrecision(modelValue.value, props.precision)
-      : modelValue.value?.toString();
-    return;
-  }
-  modelValue.value = rounded;
+  inputValue.value = getFormattedValue.value(parseFloat(inputValue.value));
+  modelValue.value = parseFloat(inputValue.value);
 };
 
 const incrementLabel = computed(() => t.value("stepper.increment", { stepSize: props.stepSize }));
@@ -149,7 +122,6 @@ const decrementLabel = computed(() => t.value("stepper.decrement", { stepSize: p
         <OnyxLoadingIndicator v-if="props.loading" class="onyx-stepper__loading" type="circle" />
         <input
           v-else
-          ref="inputRef"
           v-model="inputValue"
           v-custom-validity
           class="onyx-stepper__native"
diff --git a/packages/sit-onyx/src/components/OnyxStepper/types.ts b/packages/sit-onyx/src/components/OnyxStepper/types.ts
index 635c274f66..9fb9eebbb8 100644
--- a/packages/sit-onyx/src/components/OnyxStepper/types.ts
+++ b/packages/sit-onyx/src/components/OnyxStepper/types.ts
@@ -24,42 +24,25 @@ export type OnyxStepperProps = FormInjectedProps &
      */
     max?: number;
     /**
-     * Incremental step.
-     * @deprecated
+     * Defines how much the value is adjusted when clicking the +/- button.
      */
-    step?: number; // step-mismatch + step-increment
-
+    stepSize?: number;
     /**
-     * Number of decimal places to show. Can also be negative. Value will be rounded if needed
-     * to match the specified precision.
+     * Number of decimal places to show. Can also be negative. Value will be rounded if needed to match the specified precision.
      *
      * @example For `precision=2` with `modelValue=5`, "5.00" will be displayed.
      * @example For `precision=-2` with `modelValue=60`, "100" will be displayed.
-     * @default undefined
+     * @example For `precision=0`, only full numbers without decimals will be displayed.
      */
     precision?: number;
-
-    /**
-     * Defines how much the value is adjusted when clicking the +/- button.
-     *
-     * @default 1
-     */
-    stepSize?: number;
     /**
-     * Defines step size for valid/allowed values. Can be independent of the `stepSize` property.
-     * Uses the `min` property as base so if defining min=3 with validStepSize=2, only odd numbers will
+     * Defines step size for valid/allowed values. Will show an error if invalid value is entered.
+     * Can be independent of the `stepSize` property.
      *
      * @example For `validStepSize` 0.01, only multiples of 0.01 are valid (useful for currencies)
      * @example For `stepSize=4` `validStepSize=2`, only even numbers are valid and the user can adjust the value by 4 when clicking the +/- button.
-     * @example For `min=3` and `validStepSize=2`, only odd numbers will be valid
-     * @default undefined
      */
     validStepSize?: number;
-    /**
-     * Ensure no wrong number can be inputed
-     */
-    stripStep?: boolean;
-
     /**
      * Specify how to provide automated assistance in filling out the input.
      * Some autocomplete values might required specific browser permissions to be allowed by the user.
diff --git a/packages/sit-onyx/src/components/examples/GridPlayground/EditGridElementDialog/EditGridElementDialog.vue b/packages/sit-onyx/src/components/examples/GridPlayground/EditGridElementDialog/EditGridElementDialog.vue
index a2177cfa87..9918861b97 100644
--- a/packages/sit-onyx/src/components/examples/GridPlayground/EditGridElementDialog/EditGridElementDialog.vue
+++ b/packages/sit-onyx/src/components/examples/GridPlayground/EditGridElementDialog/EditGridElementDialog.vue
@@ -77,6 +77,7 @@ const handleCheckboxChange = (isChecked: boolean, breakpoint: OnyxBreakpoint) =>
           label="Default number of columns"
           placeholder="Default number of columns"
           v-bind="STEPPER_VALIDATIONS"
+          :precision="0"
           autofocus
           required
         />
@@ -107,6 +108,7 @@ const handleCheckboxChange = (isChecked: boolean, breakpoint: OnyxBreakpoint) =>
               v-model="state.breakpoints[breakpoint]"
               :label="`Number of columns for breakpoint ${breakpoint}`"
               v-bind="STEPPER_VALIDATIONS"
+              :precision="0"
               hide-label
               :disabled="!state.columnCount"
             />
diff --git a/packages/sit-onyx/src/utils/numbers.spec.ts b/packages/sit-onyx/src/utils/numbers.spec.ts
index 052f00461d..7c6a9eb318 100644
--- a/packages/sit-onyx/src/utils/numbers.spec.ts
+++ b/packages/sit-onyx/src/utils/numbers.spec.ts
@@ -1,29 +1,5 @@
 import { describe, expect, it } from "vitest";
-import { applyLimits, isDivisible, roundToPrecision } from "./numbers";
-
-// Tests for isDivisible function
-describe("isDivisible", () => {
-  it("returns true if number is divisible by precision", () => {
-    expect(isDivisible(10, 2)).toBe(true);
-    expect(isDivisible(15, 5)).toBe(true);
-    expect(isDivisible(0.4, 0.2)).toBe(true);
-  });
-
-  it("returns false if number is not divisible by precision", () => {
-    expect(isDivisible(10, 3)).toBe(false);
-    expect(isDivisible(15, 4)).toBe(false);
-    expect(isDivisible(0.5, 0.3)).toBe(false);
-  });
-
-  it("returns true if number is 0 and precision is non-zero", () => {
-    expect(isDivisible(0, 1)).toBe(true);
-    expect(isDivisible(0, 0.1)).toBe(true);
-  });
-
-  it("returns false if precision is 0 (division by zero)", () => {
-    expect(isDivisible(10, 0)).toBe(false);
-  });
-});
+import { applyLimits, roundToPrecision } from "./numbers";
 
 // Tests for applyLimits function
 describe("applyLimits", () => {
diff --git a/packages/sit-onyx/src/utils/numbers.ts b/packages/sit-onyx/src/utils/numbers.ts
index fdad893f00..0a5d9da537 100644
--- a/packages/sit-onyx/src/utils/numbers.ts
+++ b/packages/sit-onyx/src/utils/numbers.ts
@@ -1,14 +1,3 @@
-/**
- * Checks if a given number is divisible by a specified precision.
- *
- * @param number - The number to check for divisibility.
- * @param precision - The precision (step size) to check divisibility against.
- * @returns `true` if `number` is divisible by `precision`, otherwise `false`.
- */
-export const isDivisible = (number: number, precision: number): boolean => {
-  const quotient = number / precision;
-  return quotient % 1 === 0;
-};
 /**
  * Applies minimum and maximum limits to a given number.
  *
@@ -32,15 +21,11 @@ export const applyLimits = (
  * Supports both decimal and whole-number rounding based on the precision provided.
  *
  * @param value - The number to round, or `undefined` to return an empty string.
- * @param precision - The number of decimal places for rounding (e.g., 0.01 for 2 decimals).
+ * @param precision - The number of decimal places for rounding (e.g., 0.01 for 2 decimals). Can also be negative.
  * @returns The rounded number as a string. Returns an empty string if `value` is `undefined`.
  */
-export const roundToPrecision = (value: number | undefined, precision: number): string => {
-  if (value === undefined) return "";
-  if (precision > 0) {
-    return value.toFixed(precision);
-  }
-  const places = precision;
-  const factor = Math.pow(10, places);
+export const roundToPrecision = (value: number, precision: number): string => {
+  if (precision >= 0) return value.toFixed(precision);
+  const factor = Math.pow(10, precision);
   return (Math.round(value * factor) / factor).toString();
 };

From f3dea412134c6e113c75f4135c6e6d85ff66b1ba Mon Sep 17 00:00:00 2001
From: ChristianBusshoff <christian.busshoff@mail.schwarz>
Date: Fri, 3 Jan 2025 11:25:21 +0100
Subject: [PATCH 7/8] fix test

---
 packages/sit-onyx/src/utils/numbers.ts | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/packages/sit-onyx/src/utils/numbers.ts b/packages/sit-onyx/src/utils/numbers.ts
index 0a5d9da537..fa3ea61824 100644
--- a/packages/sit-onyx/src/utils/numbers.ts
+++ b/packages/sit-onyx/src/utils/numbers.ts
@@ -25,7 +25,7 @@ export const applyLimits = (
  * @returns The rounded number as a string. Returns an empty string if `value` is `undefined`.
  */
 export const roundToPrecision = (value: number, precision: number): string => {
-  if (precision >= 0) return value.toFixed(precision);
+  if (value && precision >= 0) return value.toFixed(precision);
   const factor = Math.pow(10, precision);
   return (Math.round(value * factor) / factor).toString();
 };

From b58c5389da2f5393a6c37d2b4fdd4d3af65673bd Mon Sep 17 00:00:00 2001
From: ChristianBusshoff <christian.busshoff@mail.schwarz>
Date: Fri, 3 Jan 2025 11:30:41 +0100
Subject: [PATCH 8/8] changes

---
 packages/sit-onyx/src/utils/numbers.ts | 3 ++-
 1 file changed, 2 insertions(+), 1 deletion(-)

diff --git a/packages/sit-onyx/src/utils/numbers.ts b/packages/sit-onyx/src/utils/numbers.ts
index fa3ea61824..3a7adb9a58 100644
--- a/packages/sit-onyx/src/utils/numbers.ts
+++ b/packages/sit-onyx/src/utils/numbers.ts
@@ -25,7 +25,8 @@ export const applyLimits = (
  * @returns The rounded number as a string. Returns an empty string if `value` is `undefined`.
  */
 export const roundToPrecision = (value: number, precision: number): string => {
-  if (value && precision >= 0) return value.toFixed(precision);
+  if (!value) return "";
+  if (precision >= 0) return value.toFixed(precision);
   const factor = Math.pow(10, precision);
   return (Math.round(value * factor) / factor).toString();
 };