Skip to content

Settle the picker's slots and names for 2.0.0, and fix eight defects - #367

Merged
smelfungus merged 12 commits into
masterfrom
pre-release-fixes
Sep 27, 2026
Merged

smelfungus merged 12 commits into
masterfrom
pre-release-fixes

Conversation

@smelfungus

Copy link
Copy Markdown
Member

API that 2.0.0 would otherwise freeze:

  • Picker slots take a part. plane, channelSlider and alphaSlider are handed a PlanePart, ChannelSliderPart or AlphaSliderPart in place of positional arguments, sealed so they can gain members. A parameter rather than a receiver: a caller's own state would hide a receiver's, and in the dialog the slot would edit the caller's state without a compile error. ColorPickerDefaults.plane(), channelSlider() and alphaSlider() go; ColorPickerDefaults.Plane(part) keeps the plane's sizing.
  • A replaced part inherits the picker's thumb, coloringMode and onValueChangeFinished, as it already inherited colors, shapes, dimensions and enabled. BasicColorPicker takes coloringMode and onValueChangeFinished, and ColoringMode.current(channel) is the coloring a slider takes.
  • ColorPickerDialogScope.state is dialogState. A caller's own state hid it, so README's dialog sample did not compile.
  • The OkLCh identifiers are spelled Oklch, as Oklab's are: Oklch, OklchColor, toOklch, state.oklch, OklchColorPicker.

Defects:

  • The desktop jars are Java 17 bytecode. They were class-file 65, 1.2.0's included, so a JDK 17 app failed with UnsupportedClassVersionError.
  • The gamut edge holds where a channel's cubic term vanishes: 29.2229263°, 52.5546158° and 232.5546158° in sRGB, three hues in Display P3. Within a few millionths of a degree of one, the closed form lost its roots: oklch(0.6 0.3 29.2229263) drew mid-grey, the cusp was white, and Okhsl came out black.
  • A tap that stops a fling moves nothing on a slider or a plane. It set the value, in the dialog too.
  • The Material slider thumb is ringed under keyboard focus, as the plane's indicator is.
  • An app's RGB, HSL, HSV and HWB channels are anchored as the library's, so an app's HWB hue track is no longer flat grey, and its hue carries across.
  • CMYK round-trips a color below zero in every channel, HWB sets B to 100 − W when a conversion leaves its hue powerless, and Okhsv round-trips just past pure blue.

CI: the macOS job publishes every module locally and checks all 24 publications, and publish.yml refuses a release from anything but a v* tag.

Module JVM Android host wasm
color 240 227 227
color-compose 14 14 14
colorpicker-foundation 308 156 156
colorpicker-material3 173 4 4

apiCheck, checkNoMaterial, the removal check, the screenshot baselines (two renamed with the Oklch previews) and the Android, desktop and web samples pass.

The jvm target set no bytecode level, so Kotlin followed the JDK the build ran on and the -jvm jars came out as class-file 65, Java 21, 1.2.0's included. A Compose Desktop app on JDK 17 resolves them, since the Gradle metadata names no JVM version, and then fails with UnsupportedClassVersionError. The Android target was already at 17.
The four modules' 24 publications, their POMs and their Dokka javadoc jars were first built by publish.yml, on the release tag, where an upload to Maven Central cannot be replaced. The macOS job now publishes them to the local repository and fails if any module is missing a target.

publish.yml checked the tag against VERSION_NAME only on a tag push. Run by hand from a branch with snapshot unticked, it released whatever VERSION_NAME said, with no tag and no GitHub Release. It now refuses before doing anything else.
OkLch, OkLchColor, toOkLch, state.okLch and OkLchColorPicker capitalised the C where Oklab, Okhsl, Okhsv and Lch do not, so a name guessed from its neighbours was wrong. Oklch is also how CSS, color.js and 1.x's OklchColor spell it. The space's id and the words the components show are unchanged, and so are the screenshots.
A dialog is usually opened from a screen that holds a ColorPickerState, and a caller's local named state beats a member of a lambda's receiver. So in a slot, state.isModified resolved to the caller's picker state and did not compile, which is what README's own dialog sample did. dialogState is not a name a caller's variable takes by accident.
A picker's thumb, coloringMode and onValueChangeFinished reached only the slots it drew by default. A slot replaced to relabel one slider, as README shows, drew the default thumb and its space's default coloring, and never reported the end of an edit, while colors, shapes, dimensions and enabled did reach it. The picker now provides all three as it provides enabled: a ChannelSlider, AlphaSlider or ChannelPlane in a slot takes the picker's thumb and coloring unless given its own, and reports to the picker's onValueChangeFinished beside its own. BasicColorPicker takes coloringMode and onValueChangeFinished for that, and ColoringMode.current(channel) is the coloring a slider takes.

The slots took their arguments by position, (state, x, y), so nothing could be added to them once 2.0.0 ships. They now take a PlanePart, ChannelSliderPart or AlphaSliderPart, sealed so they can gain members. A parameter rather than a receiver: in a dialog, whose picker edits a state of its own, a caller's variable named state would hide a receiver's member, and the slot would edit the caller's state with no compile error.

ColorPickerDefaults.plane(), channelSlider() and alphaSlider() existed to forward those settings, so they go. ColorPickerDefaults.Plane(part) keeps the one default that is more than a call, the plane's sizing. The internal PlanePart, one band of a plane's raster, is now BandRaster, since the public type needs the name.
The sliders take focus and the arrow keys move them, but neither the default thumb nor the track drew anything while focused, so in a column of sliders a keyboard user could not tell which one the next key would change. The plane already rings its indicator. The slider thumb now draws the same white ring over a dark halo, only in keyboard input mode, as the plane does, and within the gap the track leaves around the thumb.
A scrolling parent that is flinging consumes the next down before its children see it, so the touch stops the fling. The slider and the plane waited for a down whether or not it was consumed, so that touch also set the value: the slider took it as a tap, and the plane wrote it on the spot. It happened in the library's own dialog, whose body scrolls. A down an ancestor has consumed now starts no gesture on either.
A channel held still on an independent track, or in the reference color a remembered hue is carried with, sits at an anchor. The library's own channels had theirs one by one, and every channel of an app's space sat at the middle of its reference range. For an app's HWB that is whiteness and blackness 50, a grey: its hue track was flat grey, and a hue chosen on one of its greys was carried into no other family. An app's HSV sat at half saturation and half value, a dull hue track. RGB, HSL, HSV and HWB channels now take their anchors from the kind of space, so an app's sit where the library's do; the library's own do not move.
Along a line of constant hue each linear channel is a cubic in chroma, and at a few hues one channel's cubic term passes through zero: 29.2229263°, 52.5546158° and 232.5546158° in sRGB, 28.1173809°, 57.9919545° and 237.9919545° in Display P3. Within a few millionths of a degree of one, the term was above the 1e-14 at which the closed form took it for zero, but small enough that Cardano's discriminant cancelled to noise, and the real roots at the edge were lost. The default mapper then drew oklch(0.6 0.3 29.2229263) as mid-grey rather than red, the sRGB cusp there was white with chroma 0, and Okhsl at that hue came out black. A cubic term below 1e-5 of the others is now taken for zero: the roots the quadratic drops lie tens of chroma out, past any gamut, and the Newton steps on the whole cubic correct the rest.
K is 1 − max(R, G, B), so it passes 1 exactly when every channel is below 0. The conversion treated any key of 1 or more as black and zeroed C, M and Y, so srgb(-0.2 -0.5 -0.4) came back as srgb(-0.2 -0.2 -0.2), though the formula is singular at a key of 1 alone. Past it the division holds, and the color comes back unchanged.
A hue that comes out powerless is made missing, and CSS Color 4 §4.4.1 then sets HWB's blackness to 100 − W, HWB having no colorfulness to zero. The conversion zeroed only colorfulness channels, so HWB kept its W and B: srgb(0.5 0.5 0.500004) became hwb(none 50 49.9996), and back srgb(0.500004 0.5 0.5), its leftover chroma moved from blue to red, the hue a missing one reads as. HwbColorSpace.makeAchromatic already made the move for a color taken into a conversion, and a converted color now goes through it too.
Between 264.05° and 264.21° sRGB's chroma at some lightnesses has a gap, a sliver outside sRGB, and Okhsv's square reaches over it and a little past the outer edge near the cusp, by at most 7.6e-4 of a linear channel. Converting in, Okhsv drew every chroma in to the outer edge before inverting, so a color the model itself had made came back elsewhere: okhsv(264.055 0.97 1) returned s 0.936. The model is now inverted first, and a color is drawn in to the edge only when its own S or V falls outside 0..1, as every color outside sRGB at other hues does.

Okhsl keeps its chroma below the outer edge and already round-trips, but its saturations just below 1 land in the sliver by up to 6.5e-4, and a color in the sliver is not reduced. Both KDocs now say so rather than that a color from outside sRGB always comes in at its edge.
@smelfungus
smelfungus merged commit dd4b25e into master Sep 27, 2026
2 checks passed
@smelfungus
smelfungus deleted the pre-release-fixes branch September 27, 2026 23:58
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant