diff --git a/CHANGELOG.md b/CHANGELOG.md index 0cd96171..18f48dd9 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,11 +4,15 @@ All notable changes to this project will be documented in this file. ## [Unreleased] +## [0.2.40] - 2026-03-24 + ### Added +- `PlaySlider` now has a `values` property that returns all discrete values in the range, useful for pre-caching downstream results before hitting play. - `ParallelCoordinates` now exposes a `selections` property that returns the full filtering state: completed Keep/Exclude steps plus the active brush, enabling decision-tree-style filtering audit trails. - `ParallelCoordinates` now has `keep()`, `exclude()`, and `restore()` methods for programmatic control from Python. ### Fixed +- `PlaySlider` no longer produces floating-point rounding artifacts (e.g., `0.30000000000000004`) when using fractional step values. Both the displayed label and the synced Python value are now rounded to the step's precision. - `ParallelCoordinates` axis tick labels no longer get highlighted during brush/drag interactions. ## [0.2.39] - 2026-03-17 diff --git a/demos/play_slider.py b/demos/play_slider.py index 1fddd57e..d2a88938 100644 --- a/demos/play_slider.py +++ b/demos/play_slider.py @@ -4,7 +4,7 @@ # "marimo", # "matplotlib", # "numpy", -# "wigglystuff==0.2.37", +# "wigglystuff==0.2.40", # ] # /// import marimo diff --git a/pyproject.toml b/pyproject.toml index 9485450a..de896650 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,6 +1,6 @@ [project] name = "wigglystuff" -version = "0.2.39" +version = "0.2.40" description = "Collection of Anywidget Widgets" readme = "README.md" requires-python = ">=3.10" diff --git a/uv.lock b/uv.lock index 65521fdb..40e76bdc 100644 --- a/uv.lock +++ b/uv.lock @@ -4153,7 +4153,7 @@ wheels = [ [[package]] name = "wigglystuff" -version = "0.2.39" +version = "0.2.40" source = { editable = "." } dependencies = [ { name = "anywidget" }, diff --git a/wigglystuff/play_slider.py b/wigglystuff/play_slider.py index ec0867fa..399177e2 100644 --- a/wigglystuff/play_slider.py +++ b/wigglystuff/play_slider.py @@ -68,3 +68,15 @@ def __init__( width=width, **kwargs, ) + + @property + def values(self): + """All discrete values from min_value to max_value (inclusive) at the current step.""" + step_str = str(self.step) + precision = len(step_str.rstrip("0").split(".")[-1]) if "." in step_str else 0 + result = [] + v = self.min_value + while v <= self.max_value: + result.append(round(v, precision)) + v += self.step + return result diff --git a/wigglystuff/static/play-slider.js b/wigglystuff/static/play-slider.js index 69d7b37c..22957d55 100644 --- a/wigglystuff/static/play-slider.js +++ b/wigglystuff/static/play-slider.js @@ -49,7 +49,7 @@ function render({ model, el }) { function renderValue() { const val = model.get("value"); slider.value = val; - label.textContent = val; + label.textContent = val.toFixed(getPrecision()); updateTrackFill(); } @@ -57,10 +57,18 @@ function render({ model, el }) { btn.innerHTML = model.get("playing") ? PAUSE_ICON : PLAY_ICON; } + function getPrecision() { + const s = String(model.get("step")); + const dot = s.indexOf("."); + return dot === -1 ? 0 : s.length - dot - 1; + } + function snap(val) { const step = model.get("step"); const min = model.get("min_value"); - return Math.round((val - min) / step) * step + min; + const prec = getPrecision(); + const raw = Math.round((val - min) / step) * step + min; + return parseFloat(raw.toFixed(prec)); } function tick() { @@ -117,7 +125,7 @@ function render({ model, el }) { // Manual slider input slider.addEventListener("input", () => { localUpdate = true; - model.set("value", parseFloat(slider.value)); + model.set("value", snap(parseFloat(slider.value))); model.save_changes(); updateTrackFill(); });