Repository: adobe/spectrum-web-components (TypeScript)

Issue:
[Bug]: Editable Slider not rounding values 
### Code of conduct

- [X] I agree to follow this project's code of conduct.

### Impacted component(s)

Slider

### Expected behavior

Editable Slider onChange `event.target.value` should print 0.56 after adding`0.55999999999` on the NumberField 

### Actual behavior

Slider is printing 0.55999999999 instead of 0.56

### Screenshots

<img width="1272" alt="Screenshot 2024-03-21 at 6 24 26 PM" src="https://github.com/adobe/spectrum-web-components/assets/8790510/e7e6bb0a-bcfb-496c-92f2-cf92f27d885a">


### What browsers are you seeing the problem in?

Firefox, Chrome, Safari, Microsoft Edge

### How can we reproduce this issue?

Following are steps :-

Change min="0.25" to min="0.10"
In textbox , type 0.55999999999
In change i can see 0.55999999999 is getting logged instead of 0.56
Note: this bug is reproducible for few number like 0.55999999999  and NOT for [0.5999999999](tel:05999999999). And it is reproducible only for min value less than 0.11. 

JIRA bug: https://jira.corp.adobe.com/browse/CCEX-102612

### Sample code that illustrates the problem

_No response_

### Logs taken while reproducing problem

_No response_


Discussion: Would this be considered a duplicate of #3817?



Fix (patch):
diff --git a/packages/number-field/src/NumberField.ts b/packages/number-field/src/NumberField.ts
index a85afc41a2a..0c80f504704 100644
--- a/packages/number-field/src/NumberField.ts
+++ b/packages/number-field/src/NumberField.ts
@@ -495,7 +495,9 @@ export class NumberField extends TextfieldBase {
         // Step shouldn't validate when 0...
         if (this.step) {
             const min = typeof this.min !== 'undefined' ? this.min : 0;
-            const moduloStep = (value - min) % this.step;
+            const moduloStep = parseFloat(
+                this.valueFormatter.format((value - min) % this.step)
+            );
             const fallsOnStep = moduloStep === 0;
             if (!fallsOnStep) {
                 const overUnder = Math.round(moduloStep / this.step);
@@ -510,6 +512,7 @@ export class NumberField extends TextfieldBase {
                     value -= this.step;
                 }
             }
+            value = parseFloat(this.valueFormatter.format(value));
         }
         value *= signMultiplier;
         return value;
@@ -560,9 +563,27 @@ export class NumberField extends TextfieldBase {
             : this._numberFormatter;
     }
 
+    protected clearValueFormatterCache(): void {
+        this._valueFormatter = undefined;
+    }
+    protected get valueFormatter(): NumberFormatter {
+        if (!this._valueFormatter) {
+            const digitsAfterDecimal = this.step
+                ? this.step != Math.floor(this.step)
+                    ? this.step.toString().split('.')[1].length
+                    : 0
+                : 0;
+            this._valueFormatter = new NumberFormatter(
+                this.languageResolver.language,
+                { maximumFractionDigits: digitsAfterDecimal }
+            );
+        }
+
+        return this._valueFormatter;
+    }
     private _numberFormatter?: NumberFormatter;
     private _numberFormatterFocused?: NumberFormatter;
-
+    private _valueFormatter?: NumberFormatter;
     protected get numberParser(): NumberParser {
         if (!this._numberParser || !this._numberParserFocused) {
             const {
@@ -685,6 +706,9 @@ export class NumberField extends TextfieldBase {
             );
             this.value = value;
         }
+        if (changes.has('step')) {
+            this.clearValueFormatterCache();
+        }
         super.update(changes);
     }
 
diff --git a/packages/slider/stories/slider.stories.ts b/packages/slider/stories/slider.stories.ts
index 661504af288..8c8cbcc7ea3 100644
--- a/packages/slider/stories/slider.stories.ts
+++ b/packages/slider/stories/slider.stories.ts
@@ -450,6 +450,32 @@ editableWithDefaultValue.swc_vrt = {
     skip: true,
 };
 
+export const editableWithFractionValue = (
+    args: StoryArgs = {}
+): TemplateResult => {
+    return html`
+        <div style="width: 500px; margin: 12px 20px;">
+            <sp-slider
+                editable
+                max="255"
+                min="0.1"
+                value="0.5"
+                step="0.01"
+                default-value="18"
+                @input=${handleEvent(args)}
+                @change=${handleEvent(args)}
+                ...=${spreadProps(args)}
+            >
+                Angle
+            </sp-slider>
+        </div>
+    `;
+};
+
+editableWithFractionValue.swc_vrt = {
+    skip: true,
+};
+
 export const editableDisabled = (args: StoryArgs = {}): TemplateResult => {
     return html`
         <div style="width: 500px; margin: 12px 20px;">