From f27c3299c042f3f4887aa711b6dbf39d9857f45a Mon Sep 17 00:00:00 2001 From: Samuel Reichert Date: Tue, 19 May 2026 11:13:01 +0200 Subject: [PATCH 1/5] fix(slider-web): preserve trailing zeros in mark labels when decimalPlaces configured --- .../src/utils/__tests__/marks.spec.ts | 30 +++++++++++++++++++ .../slider-web/src/utils/marks.ts | 5 ++-- 2 files changed, 33 insertions(+), 2 deletions(-) create mode 100644 packages/pluggableWidgets/slider-web/src/utils/__tests__/marks.spec.ts diff --git a/packages/pluggableWidgets/slider-web/src/utils/__tests__/marks.spec.ts b/packages/pluggableWidgets/slider-web/src/utils/__tests__/marks.spec.ts new file mode 100644 index 0000000000..82d94fd5fa --- /dev/null +++ b/packages/pluggableWidgets/slider-web/src/utils/__tests__/marks.spec.ts @@ -0,0 +1,30 @@ +import { createMarks } from "../marks"; + +describe("createMarks", () => { + it("forces trailing zeros when decimalPlaces > 0 and value is whole number", () => { + const marks = createMarks({ numberOfMarks: 2, decimalPlaces: 2, min: 0, max: 10 }); + expect(marks).toBeDefined(); + expect(marks![0]).toBe("0.00"); + expect(marks![5]).toBe("5.00"); + expect(marks![10]).toBe("10.00"); + }); + + it("forces trailing zeros when decimalPlaces > 0 and value has fewer decimals", () => { + const marks = createMarks({ numberOfMarks: 2, decimalPlaces: 2, min: 0, max: 9.2 }); + expect(marks).toBeDefined(); + expect(marks![4.6]).toBe("4.60"); + expect(marks![9.2]).toBe("9.20"); + }); + + it("does not add decimal places when decimalPlaces is 0", () => { + const marks = createMarks({ numberOfMarks: 4, decimalPlaces: 0, min: 0, max: 100 }); + expect(marks).toBeDefined(); + expect(marks![0]).toBe("0"); + expect(marks![25]).toBe("25"); + expect(marks![100]).toBe("100"); + }); + + it("returns undefined when numberOfMarks is 0", () => { + expect(createMarks({ numberOfMarks: 0, decimalPlaces: 2, min: 0, max: 100 })).toBeUndefined(); + }); +}); diff --git a/packages/pluggableWidgets/slider-web/src/utils/marks.ts b/packages/pluggableWidgets/slider-web/src/utils/marks.ts index 2c27eba8e6..c70e24cf10 100644 --- a/packages/pluggableWidgets/slider-web/src/utils/marks.ts +++ b/packages/pluggableWidgets/slider-web/src/utils/marks.ts @@ -24,8 +24,9 @@ export function createMarks(params: CreateMarksParams): Marks | undefined { const interval = (max - min) / numberOfMarks; for (let i = 0; i <= numberOfMarks; i++) { - const value = parseFloat((min + i * interval).toFixed(decimalPlaces)); - marks[value] = value.toString(); + const label = (min + i * interval).toFixed(decimalPlaces); + const key = parseFloat(label); + marks[key] = label; } return marks; From b5542e0500e5e54118489a75812b286f964556fe Mon Sep 17 00:00:00 2001 From: Samuel Reichert Date: Tue, 19 May 2026 11:57:02 +0200 Subject: [PATCH 2/5] fix(slider-web): format tooltip value with configured decimalPlaces --- .../slider-web/src/components/Container.tsx | 3 +- .../__tests__/createHandleRender.spec.tsx | 111 ++++++++++++++++++ .../src/utils/createHandleRender.tsx | 7 +- 3 files changed, 118 insertions(+), 3 deletions(-) create mode 100644 packages/pluggableWidgets/slider-web/src/utils/__tests__/createHandleRender.spec.tsx diff --git a/packages/pluggableWidgets/slider-web/src/components/Container.tsx b/packages/pluggableWidgets/slider-web/src/components/Container.tsx index b26135235b..912ce02b70 100644 --- a/packages/pluggableWidgets/slider-web/src/components/Container.tsx +++ b/packages/pluggableWidgets/slider-web/src/components/Container.tsx @@ -35,7 +35,8 @@ function InnerContainer(props: InnerContainerProps): ReactElement { tooltip: props.tooltip, tooltipType: props.tooltipType, tooltipAlwaysVisible: props.tooltipAlwaysVisible, - sliderRef + sliderRef, + decimalPlaces: props.decimalPlaces }) : undefined; diff --git a/packages/pluggableWidgets/slider-web/src/utils/__tests__/createHandleRender.spec.tsx b/packages/pluggableWidgets/slider-web/src/utils/__tests__/createHandleRender.spec.tsx new file mode 100644 index 0000000000..a89922b824 --- /dev/null +++ b/packages/pluggableWidgets/slider-web/src/utils/__tests__/createHandleRender.spec.tsx @@ -0,0 +1,111 @@ +import { createRef, ReactElement } from "react"; +import { createHandleRender } from "../createHandleRender"; + +describe("createHandleRender tooltip value formatting", () => { + it("formats whole number with trailing zeros when decimalPlaces=2", () => { + const sliderRef = createRef(); + const handleRender = createHandleRender({ + tooltipType: "value", + tooltipAlwaysVisible: true, + sliderRef, + decimalPlaces: 2 + }); + + expect(handleRender).toBeDefined(); + const renderFn = handleRender!; + + // Create mock render props matching what RC Slider passes + const mockNode =
; + const mockProps = { + value: 10, + dragging: false, + index: 0, + prefixCls: "rc-slider-handle", + draggingDelete: false, + onFocus: jest.fn(), + onBlur: jest.fn() + }; + + const result = renderFn(mockNode, mockProps as any) as ReactElement; + + // The overlay should be the formatted value + expect(result.props.overlay).toBe("10.00"); + }); + + it("formats partial decimal with trailing zero when decimalPlaces=2", () => { + const sliderRef = createRef(); + const handleRender = createHandleRender({ + tooltipType: "value", + tooltipAlwaysVisible: true, + sliderRef, + decimalPlaces: 2 + }); + + const renderFn = handleRender!; + const mockNode =
; + const mockProps = { + value: 9.2, + dragging: false, + index: 0, + prefixCls: "rc-slider-handle", + draggingDelete: false, + onFocus: jest.fn(), + onBlur: jest.fn() + }; + + const result = renderFn(mockNode, mockProps as any) as ReactElement; + expect(result.props.overlay).toBe("9.20"); + }); + + it("formats value without decimals when decimalPlaces=0", () => { + const sliderRef = createRef(); + const handleRender = createHandleRender({ + tooltipType: "value", + tooltipAlwaysVisible: true, + sliderRef, + decimalPlaces: 0 + }); + + const renderFn = handleRender!; + const mockNode =
; + const mockProps = { + value: 10, + dragging: false, + index: 0, + prefixCls: "rc-slider-handle", + draggingDelete: false, + onFocus: jest.fn(), + onBlur: jest.fn() + }; + + const result = renderFn(mockNode, mockProps as any) as ReactElement; + expect(result.props.overlay).toBe("10"); + }); + + it("renders custom text tooltip ignoring decimalPlaces", () => { + const sliderRef = createRef(); + const handleRender = createHandleRender({ + tooltip: { value: "custom label" } as any, + tooltipType: "customText", + tooltipAlwaysVisible: true, + sliderRef, + decimalPlaces: 2 + }); + + const renderFn = handleRender!; + const mockNode =
; + const mockProps = { + value: 10, + dragging: false, + index: 0, + prefixCls: "rc-slider-handle", + draggingDelete: false, + onFocus: jest.fn(), + onBlur: jest.fn() + }; + + const result = renderFn(mockNode, mockProps as any) as ReactElement; + // The overlay should be the custom text div, not the formatted value + expect(result.props.overlay.props.children).toBe("custom label"); + }); +}); diff --git a/packages/pluggableWidgets/slider-web/src/utils/createHandleRender.tsx b/packages/pluggableWidgets/slider-web/src/utils/createHandleRender.tsx index 5b1a86bdc4..349176c147 100644 --- a/packages/pluggableWidgets/slider-web/src/utils/createHandleRender.tsx +++ b/packages/pluggableWidgets/slider-web/src/utils/createHandleRender.tsx @@ -10,26 +10,29 @@ type CreateHandleRenderProps = { tooltipType: "value" | "customText"; tooltipAlwaysVisible: boolean; sliderRef: RefObject; + decimalPlaces: number; }; export function createHandleRender({ tooltip, tooltipType, tooltipAlwaysVisible, - sliderRef + sliderRef, + decimalPlaces }: CreateHandleRenderProps): RcSliderProps["handleRender"] | undefined { const isCustomText = tooltipType === "customText"; const handleRender: RcSliderProps["handleRender"] = (node, props) => { const { dragging, index, ...restProps } = props; const overlay =
{tooltip?.value ?? ""}
; + const formattedValue = restProps.value.toFixed(decimalPlaces); return ( sliderRef.current ?? document.body} defaultVisible prefixCls="rc-slider-tooltip" - overlay={isCustomText ? overlay : restProps.value} + overlay={isCustomText ? overlay : formattedValue} trigger={["hover", "click", "focus"]} visible={tooltipAlwaysVisible || dragging} placement="top" From 0ff076fa4f891bc6dec8af51b9a07c470101a5a5 Mon Sep 17 00:00:00 2001 From: Samuel Reichert Date: Tue, 19 May 2026 12:39:28 +0200 Subject: [PATCH 3/5] chore(slider-web): add changelog entry for decimal places formatting fix --- packages/pluggableWidgets/slider-web/CHANGELOG.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/packages/pluggableWidgets/slider-web/CHANGELOG.md b/packages/pluggableWidgets/slider-web/CHANGELOG.md index 20079df8d5..0753d662c3 100644 --- a/packages/pluggableWidgets/slider-web/CHANGELOG.md +++ b/packages/pluggableWidgets/slider-web/CHANGELOG.md @@ -6,6 +6,10 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), ## [Unreleased] +### Fixed + +- We fixed mark labels and tooltip values not preserving trailing zeros when decimal places are configured (e.g., `10` now displays as `10.00` and `9.2` as `9.20` when two decimal places are set). + ## [3.0.2] - 2026-02-19 ### Fixed From 9fbd451beca975924cfb522eb42f9a6ff694a164 Mon Sep 17 00:00:00 2001 From: Samuel Reichert Date: Tue, 19 May 2026 14:27:17 +0200 Subject: [PATCH 4/5] refactor(slider-web): memoize handleRender, clean up tests, add min===max guard test --- .../slider-web/src/components/Container.tsx | 22 ++-- .../__tests__/createHandleRender.spec.tsx | 108 ++++-------------- .../src/utils/__tests__/marks.spec.ts | 4 + 3 files changed, 41 insertions(+), 93 deletions(-) diff --git a/packages/pluggableWidgets/slider-web/src/components/Container.tsx b/packages/pluggableWidgets/slider-web/src/components/Container.tsx index 912ce02b70..9a708ec1f0 100644 --- a/packages/pluggableWidgets/slider-web/src/components/Container.tsx +++ b/packages/pluggableWidgets/slider-web/src/components/Container.tsx @@ -30,15 +30,19 @@ interface InnerContainerProps extends SliderContainerProps { function InnerContainer(props: InnerContainerProps): ReactElement { const sliderRef = useRef(null); - const handleRender = props.showTooltip - ? createHandleRender({ - tooltip: props.tooltip, - tooltipType: props.tooltipType, - tooltipAlwaysVisible: props.tooltipAlwaysVisible, - sliderRef, - decimalPlaces: props.decimalPlaces - }) - : undefined; + const handleRender = useMemo( + () => + props.showTooltip + ? createHandleRender({ + tooltip: props.tooltip, + tooltipType: props.tooltipType, + tooltipAlwaysVisible: props.tooltipAlwaysVisible, + sliderRef, + decimalPlaces: props.decimalPlaces + }) + : undefined, + [props.showTooltip, props.tooltip, props.tooltipType, props.tooltipAlwaysVisible, props.decimalPlaces] + ); const { onChange } = useOnChangeDebounced({ valueAttribute: props.valueAttribute, onChange: props.onChange }); const marks = useMarks({ diff --git a/packages/pluggableWidgets/slider-web/src/utils/__tests__/createHandleRender.spec.tsx b/packages/pluggableWidgets/slider-web/src/utils/__tests__/createHandleRender.spec.tsx index a89922b824..d149907aa0 100644 --- a/packages/pluggableWidgets/slider-web/src/utils/__tests__/createHandleRender.spec.tsx +++ b/packages/pluggableWidgets/slider-web/src/utils/__tests__/createHandleRender.spec.tsx @@ -1,84 +1,38 @@ import { createRef, ReactElement } from "react"; import { createHandleRender } from "../createHandleRender"; +const defaultRenderProps = { + dragging: false, + index: 0, + prefixCls: "rc-slider-handle", + draggingDelete: false, + onFocus: jest.fn(), + onBlur: jest.fn() +}; + +const mockNode =
; + +function buildHandleRender(decimalPlaces: number, tooltipType: "value" | "customText" = "value") { + const sliderRef = createRef(); + return createHandleRender({ tooltipType, tooltipAlwaysVisible: true, sliderRef, decimalPlaces })!; +} + describe("createHandleRender tooltip value formatting", () => { it("formats whole number with trailing zeros when decimalPlaces=2", () => { - const sliderRef = createRef(); - const handleRender = createHandleRender({ - tooltipType: "value", - tooltipAlwaysVisible: true, - sliderRef, - decimalPlaces: 2 - }); - - expect(handleRender).toBeDefined(); - const renderFn = handleRender!; - - // Create mock render props matching what RC Slider passes - const mockNode =
; - const mockProps = { - value: 10, - dragging: false, - index: 0, - prefixCls: "rc-slider-handle", - draggingDelete: false, - onFocus: jest.fn(), - onBlur: jest.fn() - }; - - const result = renderFn(mockNode, mockProps as any) as ReactElement; - - // The overlay should be the formatted value + const result = buildHandleRender(2)(mockNode, { ...defaultRenderProps, value: 10 } as any) as ReactElement; expect(result.props.overlay).toBe("10.00"); }); it("formats partial decimal with trailing zero when decimalPlaces=2", () => { - const sliderRef = createRef(); - const handleRender = createHandleRender({ - tooltipType: "value", - tooltipAlwaysVisible: true, - sliderRef, - decimalPlaces: 2 - }); - - const renderFn = handleRender!; - const mockNode =
; - const mockProps = { - value: 9.2, - dragging: false, - index: 0, - prefixCls: "rc-slider-handle", - draggingDelete: false, - onFocus: jest.fn(), - onBlur: jest.fn() - }; - - const result = renderFn(mockNode, mockProps as any) as ReactElement; + const result = buildHandleRender(2)(mockNode, { + ...defaultRenderProps, + value: 9.2 + } as any) as ReactElement; expect(result.props.overlay).toBe("9.20"); }); it("formats value without decimals when decimalPlaces=0", () => { - const sliderRef = createRef(); - const handleRender = createHandleRender({ - tooltipType: "value", - tooltipAlwaysVisible: true, - sliderRef, - decimalPlaces: 0 - }); - - const renderFn = handleRender!; - const mockNode =
; - const mockProps = { - value: 10, - dragging: false, - index: 0, - prefixCls: "rc-slider-handle", - draggingDelete: false, - onFocus: jest.fn(), - onBlur: jest.fn() - }; - - const result = renderFn(mockNode, mockProps as any) as ReactElement; + const result = buildHandleRender(0)(mockNode, { ...defaultRenderProps, value: 10 } as any) as ReactElement; expect(result.props.overlay).toBe("10"); }); @@ -90,22 +44,8 @@ describe("createHandleRender tooltip value formatting", () => { tooltipAlwaysVisible: true, sliderRef, decimalPlaces: 2 - }); - - const renderFn = handleRender!; - const mockNode =
; - const mockProps = { - value: 10, - dragging: false, - index: 0, - prefixCls: "rc-slider-handle", - draggingDelete: false, - onFocus: jest.fn(), - onBlur: jest.fn() - }; - - const result = renderFn(mockNode, mockProps as any) as ReactElement; - // The overlay should be the custom text div, not the formatted value + })!; + const result = handleRender(mockNode, { ...defaultRenderProps, value: 10 } as any) as ReactElement; expect(result.props.overlay.props.children).toBe("custom label"); }); }); diff --git a/packages/pluggableWidgets/slider-web/src/utils/__tests__/marks.spec.ts b/packages/pluggableWidgets/slider-web/src/utils/__tests__/marks.spec.ts index 82d94fd5fa..8bc3458350 100644 --- a/packages/pluggableWidgets/slider-web/src/utils/__tests__/marks.spec.ts +++ b/packages/pluggableWidgets/slider-web/src/utils/__tests__/marks.spec.ts @@ -27,4 +27,8 @@ describe("createMarks", () => { it("returns undefined when numberOfMarks is 0", () => { expect(createMarks({ numberOfMarks: 0, decimalPlaces: 2, min: 0, max: 100 })).toBeUndefined(); }); + + it("returns undefined when min equals max", () => { + expect(createMarks({ numberOfMarks: 4, decimalPlaces: 2, min: 5, max: 5 })).toBeUndefined(); + }); }); From 4e14487a53a7d374eabd280123f991ab3d8390f4 Mon Sep 17 00:00:00 2001 From: Samuel Reichert Date: Tue, 19 May 2026 14:43:31 +0200 Subject: [PATCH 5/5] refactor(slider-web): inline tooltip value formatting, remove unused variable --- .../slider-web/src/utils/createHandleRender.tsx | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/packages/pluggableWidgets/slider-web/src/utils/createHandleRender.tsx b/packages/pluggableWidgets/slider-web/src/utils/createHandleRender.tsx index 349176c147..d5a9760a55 100644 --- a/packages/pluggableWidgets/slider-web/src/utils/createHandleRender.tsx +++ b/packages/pluggableWidgets/slider-web/src/utils/createHandleRender.tsx @@ -25,14 +25,13 @@ export function createHandleRender({ const handleRender: RcSliderProps["handleRender"] = (node, props) => { const { dragging, index, ...restProps } = props; const overlay =
{tooltip?.value ?? ""}
; - const formattedValue = restProps.value.toFixed(decimalPlaces); return ( sliderRef.current ?? document.body} defaultVisible prefixCls="rc-slider-tooltip" - overlay={isCustomText ? overlay : formattedValue} + overlay={isCustomText ? overlay : restProps.value.toFixed(decimalPlaces)} trigger={["hover", "click", "focus"]} visible={tooltipAlwaysVisible || dragging} placement="top"