Skip to content

feat(core): migrate ColorController to 2nd-gen and optimize with colorjs.io#6450

Open
blunteshwar wants to merge 6 commits into
mainfrom
color-controller-migration
Open

feat(core): migrate ColorController to 2nd-gen and optimize with colorjs.io#6450
blunteshwar wants to merge 6 commits into
mainfrom
color-controller-migration

Conversation

@blunteshwar

Copy link
Copy Markdown
Contributor

Description

Relocates ColorController from 1st-gen reactive-controllers into @spectrum-web-components/core controllers, and optimizes the implementation using colorjs.io built-ins without changing any output color formats.

This is a controller relocation plus code cleanup, not a component migration. The 1st-gen ColorController and its consumers (ColorSlider, ColorArea, ColorWheel, ColorField) are untouched.

Supersedes #6449 (closed when its branch was renamed to color-controller-migration).

What changed

  • New 2nd-gen/packages/core/controllers/color-controller/ (src/color-controller.ts + index.ts), exported from controllers/index.ts.
  • Added colorjs.io@0.5.2 dependency (pinned to match 1st-gen), package exports + typesVersions entries, and externalized colorjs.io in the build so it is not bundled into output.
  • Ported the controller test suite as a Storybook play test at test/color-controller.test.ts, following the folder and file structure of placement-controller. Assertions run as play functions against a minimal non-rendering host (6 stories cover the original 28 cases).

Optimizations (no output-format change)

  • Hoisted the four regex arrays to module-level constants (were rebuilt on every validateColorString call).
  • Removed dead getColor formats: hsva/hsla are not real colorjs.io spaces and throw on .to().
  • Deduped space-id-from-object detection into a shared helper used by both the color setter and colorValue getter.
  • Centralized the three near-identical Dev Mode window.__swc warning blocks into one debugWarn helper, guarded with typeof window so the controller is safe in SSR/Node environments.
  • Tightened internal types and removed loose casts.

The format-sensitive logic (colorValue string building, _getHexString forced-alpha) was intentionally kept manual: colorjs.io's own toString() emits modern space-separated, significant-digit CSS and drops alpha=1, which would break the pinned comma-separated / Math.round / forced-alpha output.

Motivation and context

The 2nd-gen core package needs ColorController available for the gen-2 color component migrations. Moving it now, with the long-standing implementation cleaned up, avoids carrying forward dead code and rebuilt-per-call regexes.

Related issue(s)

  • fixes [N/A — internal 2nd-gen migration task]

Author's checklist

  • I have read the CONTRIBUTING and PULL_REQUESTS documents.
  • I have reviewed the Accessibility Practices for this feature, see: Aria Practices
  • I have added automated tests to cover my changes.
  • I have included a well-written changeset if my change needs to be published.
  • I have included updated documentation if my change required it.

Reviewer's checklist

  • Includes a Github Issue with appropriate flag or Jira ticket number without a link
  • Includes thoughtfully written changeset if changes suggested include patch, minor, or major features
  • Automated tests cover all use cases and follow best practices for writing
  • Validated on all supported browsers
  • All VRTs are approved before the author can update Golden Hash

Manual review test cases

  • ColorController behavior parity
    1. From 2nd-gen/packages/swc, run yarn vitest --run --project storybook color-controller.test.ts
    2. Confirm all stories pass
    3. Expect color-format round-trips (rgb/rgba, hsl/hsla, hsv/hsva, hex with/without alpha, percentage rgb) to match 1st-gen exactly

Device review

  • Did it pass in Desktop?
  • Did it pass in (emulated) Mobile?
  • Did it pass in (emulated) iPad?

Accessibility testing checklist

ColorController is a non-rendering reactive controller with no DOM, focusable parts, or screen-reader surface of its own.

  • Keyboard (required — document steps below)

    1. No focusable parts: the controller renders no DOM and exposes no interactive elements.
    2. Confirm no regressions in consuming color components (ColorSlider, ColorArea, ColorWheel, ColorField), which are unchanged by this PR.
  • Screen reader (required — document steps below)

    1. No roles, names, or live regions are emitted by the controller.
    2. Color value parsing/formatting is unchanged, so any AT-facing values surfaced by consuming components remain identical.

…rjs.io

Relocate ColorController from 1st-gen reactive-controllers into
@spectrum-web-components/core controllers, and optimize the implementation
using colorjs.io built-ins without changing any output color formats.

Changes:
- Add controllers/color-controller (src + index) and export from
  controllers/index.ts; add colorjs.io@0.5.2 dependency, package exports,
  and typesVersions; externalize colorjs.io in the build.
- Optimizations (no output-format change): hoist regex arrays to module
  constants, remove dead getColor formats (hsva/hsla are not real colorjs
  spaces and throw), dedupe space-id detection into a shared helper,
  centralize Dev Mode warnings behind a typeof-window guard for SSR/Node
  safety, and tighten internal types.
- Port the controller test suite as a pure-logic Node unit test under
  test/__unit__, activating the dormant core-unit Vitest project.
- Exclude test/__unit__ from the Storybook story indexer so unit tests are
  not indexed as CSF.

1st-gen ColorController and its consumers are untouched.
…r test

Replace the Node-unit `test/__unit__/color-controller.test.ts` with a
Storybook play test at `test/color-controller.test.ts`, matching the folder
and file structure used by placement-controller. The assertions run as `play`
functions in the storybook Vitest project against a minimal non-rendering host.

Reverts the now-unneeded scaffolding: the dormant `core-unit` Vitest project
in swc/vitest.config.js stays commented out, and the Storybook story indexer
no longer needs a `__unit__` exclusion.
@blunteshwar blunteshwar requested a review from a team as a code owner June 24, 2026 12:58
@changeset-bot

changeset-bot Bot commented Jun 24, 2026

Copy link
Copy Markdown

⚠️ No Changeset found

Latest commit: bbff5d3

Merging this PR will not cause a version bump for any packages. If these changes should not result in a new version, you're good to go. If these changes should result in a version bump, you need to add a changeset.

This PR includes no changesets

When changesets are added to this PR, you'll see the packages that this PR includes changesets for and the associated semver types

Click here to learn what changesets are, and how to add one.

Click here if you're a maintainer who wants to add a changeset to this PR

@github-actions

Copy link
Copy Markdown
Contributor

📚 Branch Preview Links

🔍 First Generation Visual Regression Test Results

When a visual regression test fails (or has previously failed while working on this branch), its results can be found in the following URLs:

Deployed to Azure Blob Storage: pr-6450

If the changes are expected, update the current_golden_images_cache hash in the circleci config to accept the new images. Instructions are included in that file.
If the changes are unexpected, you can investigate the cause of the differences and update the code accordingly.

@coveralls

coveralls commented Jun 24, 2026

Copy link
Copy Markdown
Collaborator

Coverage Report for CI Build 28256271759

Coverage remained the same at 96.246%

Details

  • Coverage remained the same as the base build.
  • Patch coverage: No coverable lines changed in this PR.
  • No coverage regressions found.

Uncovered Changes

No uncovered changes found.

Coverage Regressions

No coverage regressions found.


Coverage Stats

Coverage Status
Relevant Lines: 39173
Covered Lines: 37902
Line Coverage: 96.76%
Relevant Branches: 6460
Covered Branches: 6018
Branch Coverage: 93.16%
Branches in Coverage %: Yes
Coverage Strength: 458.53 hits per line

💛 - Coveralls

Add a per-unit MDX docs page and Storybook stories for ColorController,
mirroring the placement-controller structure: a `demo-color-playground` /
`demo-color-formats` demo host, a stories file with `controller` meta tag and
behavior stories, and an MDX page with What it does, Basic usage, Behaviors,
Accessibility, a hand-authored API section, and an appendix.
@pfulton pfulton added Status:Ready for review PR ready for review or re-review. 2nd gen These issues or PRs map to our 2nd generation work to modernizing infrastructure. labels Jun 24, 2026

@nikkimk nikkimk left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

lgtm - the docs for this were helpful

Comment on lines +188 to +195
colorController.color = new Color('srgb', [0.5, 0.25, 0.75]);
colorController.colorOrigin = { r: '50%', g: '25%', b: '75%' };
expect(colorController.colorValue).toEqual({
r: '128%',
g: '64%',
b: '191%',
a: 1,
});

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Something is not adding up here how come these percentages are more than 100 lol... shouldn't this be

 expect(colorController.colorValue).toEqual({
    r: '50%',
    g: '25%',
    b: '75%',
    a: 1,
  });

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You are correct, In the color-controller while converting in % I was multiplying with 255 instead of 100.
Its fixed now

The srgb percentage-object branch of `colorValue` produced percentages above
100 (e.g. 0.5 -> "128%") because it multiplied the 0–1 channel by 255 (the
8-bit scale) and appended "%". It also used `String.search('%')` as a boolean,
which returns -1 (truthy) for non-percent strings, so the percent branch ran
for any string channel.

Multiply by 100 for percentages (0.5 -> "50%") and guard with `.includes('%')`.
This is a behavior fix to a long-standing Gen1 bug carried into the migration;
the test expectation is updated to the corrected `{ r: '50%', g: '25%', b: '75%' }`.
@blunteshwar blunteshwar requested a review from TarunAdobe June 26, 2026 08:51
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

2nd gen These issues or PRs map to our 2nd generation work to modernizing infrastructure. Status:Ready for review PR ready for review or re-review.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

5 participants