Skip to content

feat!: SPEC_2026, SchemeCmf, and PaletteStyle sealed interface#491

Open
jordond wants to merge 10 commits intomainfrom
feat/upstream-2026-spec-cmf
Open

feat!: SPEC_2026, SchemeCmf, and PaletteStyle sealed interface#491
jordond wants to merge 10 commits intomainfrom
feat/upstream-2026-spec-cmf

Conversation

@jordond
Copy link
Copy Markdown
Owner

@jordond jordond commented Mar 30, 2026

Summary

  • Adds upstream SPEC_2026 color spec and SchemeCmf for the CMF (Color, Material, Finish) dual-source color scheme
  • Converts PaletteStyle from an enum to a sealed interface — existing styles become data objects, Cmf becomes a @Poko class with an optional tertiarySourceColor: Color? parameter
  • Users can now pass a second source color through the high-level Compose API (PaletteStyle.Cmf(tertiarySourceColor = Color.Blue)) instead of needing SchemeCmf directly
  • Updates README with 4.x → 5.x migration guide, CMF usage docs, and SPEC_2026 info
  • Notes that Material Expressive (5.0.0-alpha) moves to 6.x

Breaking changes

  • PaletteStyle is a sealed interface, not an enum — PaletteStyle.entries / values() no longer exist
  • PaletteStyle.Cmf is now a class (constructed with PaletteStyle.Cmf()) instead of an enum entry
  • DynamicScheme constructor accepts sourceColorHctList (existing single-source constructors still work)

Test plan

  • ./gradlew :material-kolor:jvmTest passes
  • Verify API dump is correct for all targets
  • Test CMF with single source and dual source colors in the demo app

jordond added 10 commits March 30, 2026 14:24
Upstream issues #469, #470, #479 introduce a new 2026 color spec
and a CMF (Color Material Form) variant supporting dual source colors.
Primary constructor now takes List<Hct> instead of single Hct.
sourceColorHct is derived as the first element. Updated from() to
pass the list, and maybeFallbackSpecVersion to handle CMF (passes
through) and downgrade SPEC_2026 to SPEC_2025 for non-CMF variants.
Changed Builder.extendSpecVersion from == to >= so that SPEC_2026
correctly activates both 2025 and 2026 color extensions in the
layered spec system. Added top-level DynamicColor.extendSpecVersion()
extension function for cleaner usage in ColorSpec implementations.
- ColorSpecs: add SPEC_2026 instance routing
- MaterialDynamicColors: default colorSpec changed to ColorSpec2026
- ColorSpec2021: add else throw for unsupported CMF variant in palettes
- ColorSpec2025: make class open to allow ColorSpec2026 extension
ColorSpec2026 extends ColorSpec2025 with CMF-specific surface tones,
chroma multipliers, tMaxC/tMinC helpers, conditional on-fixed
backgrounds (tone > 57), *Dim remapping, and phone-platform-gated
toneDeltaPairs.

SchemeCmf supports dual source colors via sourceColorHctList with
dynamic error hue lookup and tertiary palette from second source.
Add SPEC_2026 to upstream Java ColorSpec enum and update test
mapping to prevent exhaustive when-expression compile errors.
CMF works with a single seed color (tertiary falls back to primary).
For dual source color support, use SchemeCmf directly.
Updates to the latest upstream commit which includes the 2026 spec
and CMF variant. Test now maps SPEC_2026 instead of skipping it.
…support

PaletteStyle is now a sealed interface with data objects for existing
styles and a @poko class for Cmf that accepts an optional
tertiarySourceColor. This lets users pass a second source color through
the high-level Compose API instead of needing SchemeCmf directly.

Updates README with migration guide, CMF docs, and SPEC_2026 info.
@jordond jordond changed the title feat: add 2026 Material spec and CMF variant feat!: SPEC_2026, SchemeCmf, and PaletteStyle sealed interface Mar 30, 2026
@jordond
Copy link
Copy Markdown
Owner Author

jordond commented Mar 30, 2026

Code review

Found 2 issues:

  1. DynamicScheme(...) factory function loses sourceColorHctList when PaletteStyle.Cmf is used with palette overrides — the factory always calls the single-source constructor (sourceColorHct = seedColor.toHct()), collapsing sourceColorHctList to a 1-element list. ColorSpec2026.tertiary() and tertiaryContainer() read sourceColorHctList.getOrNull(1) for tone derivation and silently fall back to the primary source tone, discarding Cmf.tertiarySourceColor.

return DynamicScheme(
sourceColorHct = seedColor.toHct(),
variant = style.asVariant,
isDark = isDark,
contrastLevel = contrastLevel,
primaryPalette = primary?.toTonalPalette() ?: defaults.primaryPalette,
secondaryPalette = secondary?.toTonalPalette() ?: defaults.secondaryPalette,
tertiaryPalette = tertiary?.toTonalPalette() ?: defaults.tertiaryPalette,
neutralPalette = neutral?.toTonalPalette() ?: defaults.neutralPalette,
neutralVariantPalette = neutralVariant?.toTonalPalette() ?: defaults.neutralVariantPalette,
errorPalette = error?.toTonalPalette() ?: defaults.errorPalette,
specVersion = specVersion,
platform = platform,
)
}

  1. High-level factory functions (toDynamicScheme, DynamicScheme(seedColor, ...)) default specVersion to ColorSpec.SpecVersion.Default which is SPEC_2021. Callers using PaletteStyle.Cmf() without explicitly passing specVersion = SPEC_2026 get an IllegalArgumentException from deep inside SchemeCmf.init with no guidance at the call site. PaletteStyle.Cmf KDoc documents the requirement, but neither factory function documents or guards it.

/**
* Create a [DynamicScheme] based on the provided colors.
*
* If a color is not provided, then the color palette will be generated from the [style] and [seedColor].
*
* @param[seedColor] The color to base the scheme on.
* @param[isDark] Whether the scheme should be dark or light.
* @param[primary] The primary color of the scheme.
* @param[secondary] The secondary color of the scheme.
* @param[tertiary] The tertiary color of the scheme.
* @param[neutral] The neutral color of the scheme.
* @param[neutralVariant] The neutral variant color of the scheme.
* @param[error] The error color of the scheme.
* @param[style] The style of the scheme.
* @param[contrastLevel] The contrast level of the scheme.
* @param[specVersion] The version of the color specification to use.
* @param[platform] The platform to use for the scheme.

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