Skip to content

feat(color-loupe): migrate sp-color-loupe component to 2nd-gen#6147

Merged
blunteshwar merged 21 commits intocolor-loupe-migrationfrom
claude-do-the-honors
Apr 22, 2026
Merged

feat(color-loupe): migrate sp-color-loupe component to 2nd-gen#6147
blunteshwar merged 21 commits intocolor-loupe-migrationfrom
claude-do-the-honors

Conversation

@blunteshwar
Copy link
Copy Markdown
Contributor

@blunteshwar blunteshwar commented Apr 7, 2026

Description

Migrates the color-loupe component from 1st-gen (sp-color-loupe) to 2nd-gen (swc-color-loupe) following the washing machine workflow. All 7 migration steps are complete and the status table has been updated.

What changed

Core package (2nd-gen/packages/core/components/color-loupe/)

  • ColorLoupe.types.ts — minimal types file with the default color constant
  • ColorLoupe.base.ts — abstract base class extending SpectrumElement; holds the two public properties (open, color)
  • index.ts — re-exports

SWC package (2nd-gen/packages/swc/components/color-loupe/)

  • ColorLoupe.ts — concrete class with render() and styles
  • color-loupe.css — token-based CSS following the S2 migration guide
  • index.tsdefineElement('swc-color-loupe', ColorLoupe) registration
  • stories/color-loupe.stories.ts — Playground, Overview, Anatomy, States, ColorDisplay (behaviors), and Accessibility stories
  • test/color-loupe.test.ts — Storybook play-function tests (defaults, open reflection, color property)
  • test/color-loupe.a11y.spec.ts — Playwright attribute test asserting aria-hidden="true" on the SVG (replaced the unusable empty toMatchAriaSnapshot which Playwright rejects even for legitimately empty trees)

Migration docs (CONTRIBUTOR-DOCS/03_project-planning/03_components/color-loupe/)

  • rendering-and-styling-migration-analysis.md — full API surface, DOM structure, factor assessment, and gen-2 delta notes (moved from 1st-gen/packages/color-loupe/MIGRATION_ANALYSIS.md)
  • accessibility-migration-analysis.md — updated
  • migration-checklist.md — all 7 steps marked complete

Status table

  • CONTRIBUTOR-DOCS/.../01_status.md — all 7 columns marked for Color Loupe

Notable changes from 1st-gen

Area Change
SVG path Fixed double-close-command bug (61.575ZZ61.575Z)
SVG mask Fixed broken mask reference (xlink:href="#path"href="#loupe-path")
xlink:href Migrated deprecated xlink:href to modern href throughout the SVG
Opacity checkerboard Replaced @spectrum-web-components/opacity-checkerboard import with an inline repeating-conic-gradient using --swc-opacity-checkerboard-* tokens and light-dark() for theme support
Drop shadow Migrated from S1 --spectrum-drop-shadow-x to S2 token("drop-shadow-elevated-*")
Token syntax All --spectrum-color-loupe-* / --mod-colorloupe-* chains replaced with token() and --swc-color-loupe-* exposed overrides
Color display Simplified render: separate div.swc-ColorLoupe-colorFill with background: ${this.color} instead of setting --spectrum-picked-color on the SVG inline style
Factor step Intentionally skipped — component is pure-presentational (~40 lines of logic), no separable state

Motivation and context

sp-color-loupe is a dependency of the color picker family. Migrating it to 2nd-gen unblocks downstream component migrations (swc-color-field, swc-color-slider, swc-color-area) and ensures it consumes Spectrum 2 design tokens.

Related issue(s)

  • Jira: SWC-1193 (WCAG 1.4.11 limitation — non-text contrast for the loupe border, documented in the accessibility analysis, not resolved in this PR)

Screenshots (if appropriate)

Visual verification via the color-loupe--overview Storybook story. No screenshot attached; teardrop shape, checkerboard, and color fill are visually unchanged from 1st-gen.

Author's checklist

  • I have read the CONTRIBUTING and PULL_REQUESTS documents.
  • I have reviewed at 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

  • Loupe renders correctly in Storybook

    1. Go to Color Loupe — Overview
    2. Verify the teardrop shape, checkerboard pattern, and blue color fill all render
    3. Expect pixel-accurate match against the 1st-gen sp-color-loupe
  • Open/close animation works

    1. Go to Color Loupe — States
    2. Toggle the open control in the Storybook controls panel
    3. Expect the loupe to animate in (opacity + transform) when open and animate out when closed
  • Color fill updates correctly

    1. Go to Color Loupe — Color display
    2. Change the color control to a color with alpha (e.g., rgba(255, 0, 0, 0.5))
    3. Expect the checkerboard to show through the transparent fill
  • Lint and unit tests pass

    1. Run yarn lint:2nd-gen
    2. Run yarn test:unit:2nd
    3. Expect zero errors
  • Playwright a11y tests pass

    1. Run yarn test:a11y:2nd
    2. Expect both color-loupe.a11y.spec.ts tests to pass — they assert aria-hidden="true" on the SVG element

Device review

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

Accessibility testing checklist

Required: Complete each applicable item and document your testing steps.

  • Keyboard (non-interactive component — no focusable parts)

    1. Go to Color Loupe — Accessibility
    2. Press Tab repeatedly through the story
    3. Expect focus to skip the loupe entirely — it has no tab stop and sets no tabindex
    4. Confirm no regressions in surrounding Storybook chrome
  • Screen reader (visual-only component — SVG is aria-hidden)

    1. Open Color Loupe — Accessibility with VoiceOver (macOS) or NVDA (Windows)
    2. Navigate through the page with VO+Right / F6
    3. Expect the loupe SVG to be completely skipped — no role, name, or value should be announced
    4. Confirm the swc-color-loupe custom element itself is not announced as an interactive control

@changeset-bot
Copy link
Copy Markdown

changeset-bot Bot commented Apr 7, 2026

⚠️ No Changeset found

Latest commit: e360348

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

@blunteshwar blunteshwar added the Status:WIP PR is a work in progress or draft label Apr 7, 2026
@Rajdeepc
Copy link
Copy Markdown
Contributor

@blunteshwar Anything blocking this migration?

@github-actions
Copy link
Copy Markdown
Contributor

github-actions Bot commented Apr 16, 2026

📚 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-6147

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.

@blunteshwar blunteshwar marked this pull request as ready for review April 17, 2026 04:55
@blunteshwar blunteshwar requested a review from a team as a code owner April 17, 2026 04:55
@blunteshwar blunteshwar changed the title feat(color-loupe): implement Color Loupe component feat(color-loupe): migrate sp-color-loupe to swc-color-loupe (2nd-gen) Apr 17, 2026
@blunteshwar blunteshwar added Status:Ready for review PR ready for review or re-review. and removed Status:WIP PR is a work in progress or draft labels Apr 17, 2026
@Rajdeepc Rajdeepc changed the title feat(color-loupe): migrate sp-color-loupe to swc-color-loupe (2nd-gen) feat(color-loupe): migrate sp-color-loupe component to 2nd-gen Apr 17, 2026
Copy link
Copy Markdown
Contributor

@5t3ph 5t3ph left a comment

Choose a reason for hiding this comment

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

Requesting changes because it looks like the implementation could be upgraded to match S2 Spectrum CSS as noted.

Comment thread 2nd-gen/packages/swc/components/color-loupe/ColorLoupe.ts
Comment thread 2nd-gen/packages/swc/components/color-loupe/color-loupe.css
@5t3ph 5t3ph self-assigned this Apr 20, 2026
@blunteshwar blunteshwar requested a review from 5t3ph April 21, 2026 04:54
Comment thread 2nd-gen/packages/swc/components/color-loupe/ColorLoupe.ts Outdated
<div class="swc-ColorLoupe-checkerboard swc-ColorLoupe--clipped"></div>
<div
class="swc-ColorLoupe-colorFill swc-ColorLoupe--clipped"
style="background: ${this.color}"
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.

I think we need to add a comment in the contributor docs stating to pass only CSS only strings from trusted sources.

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.

Good call. Added a new section "Inline CSS strings from component properties" to CONTRIBUTOR-DOCS/02_style-guide/02_typescript/09_rendering-patterns.md in commit fc8dbcf5bd.

Using this style="background: ${this.color}" line as the worked example, the new section codifies four rules:

  1. Only accept CSS strings from trusted sources
  2. Prefer structured inputs (classMap + design tokens) over free-form CSS
  3. Scope values through a CSS custom property (styleMap({'--swc-...': value})) rather than interpolating a full declaration
  4. Document the trust contract on the property's JSDoc

It also includes ✅ preferred / ✅ acceptable / ⚠️ use-with-care code examples so it's actionable in review.

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.

The second example it provides of passing this into the value of a custom property vs straight to the background would be preferred, and is the pattern also exemplified in the Spectrum CSS solution.

Copy link
Copy Markdown
Contributor Author

@blunteshwar blunteshwar Apr 22, 2026

Choose a reason for hiding this comment

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

Implemented. The template now routes this.color through a CSS custom property via styleMap, and the stylesheet consumes that property:

// ColorLoupe.ts
style=${styleMap({
  '--swc-color-loupe-picked-color': this.color,
})}
/* color-loupe.css */
.swc-ColorLoupe-colorFill {
  background: var(--swc-color-loupe-picked-color);
}

On HSV point — both background: ${this.color} and background: var(--swc-color-loupe-picked-color) hand the same string to the same browser CSS parser, so the set of accepted color formats is identical across the two patterns. The loupe always receives a CSS-valid string from its parent color picker, which is where non-CSS color-space conversion (including HSV → HSL/RGB) happens.

Comment thread 2nd-gen/packages/swc/package.json Outdated
Comment thread 2nd-gen/packages/swc/vitest.config.js Outdated
- ColorLoupe.ts: link TODO to SWC-2029 for opacity-checkerboard migration
- color-loupe.test.ts: add CSS regression steps asserting inner-loupe opacity flips with [open]
- package.json: revert unrelated customElements path change
- vitest.config.js: revert unrelated coverage threshold bumps
- CONTRIBUTOR-DOCS: document guidance for inline CSS strings from component properties

Made-with: Cursor
@blunteshwar blunteshwar requested a review from Rajdeepc April 21, 2026 07:04
Copy link
Copy Markdown
Contributor

@5t3ph 5t3ph left a comment

Choose a reason for hiding this comment

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

Thanks for addressing the render bug. A few remaining updates.

block-size: token("color-loupe-height");
pointer-events: none;
opacity: var(--_swc-color-loupe-opacity);
filter: drop-shadow(var(--swc-color-loupe-drop-shadow-x, token("drop-shadow-elevated-x")) var(--swc-color-loupe-drop-shadow-y, token("drop-shadow-elevated-y")) var(--swc-color-loupe-drop-shadow-blur, token("drop-shadow-elevated-blur")) var(--swc-color-loupe-drop-shadow-color, token("drop-shadow-elevated-color")));
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.

Per our guidelines, we are not exposing every token, only those that are actually modified by the component. This definition can be simplified to remove all of the exposed properties. Same for several other instances in this stylesheet.

I assume AI just converted the mod properties, but again, that is an anti-pattern.

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.

Done. Exposed --swc-color-loupe-* surface went from 10 properties down to 1 (only --swc-color-loupe-picked-color, which the component itself modifies on every render via the template).

Collapsed to direct token() calls (none modified by the component):

  • --swc-color-loupe-offset
  • --swc-color-loupe-animation-distance
  • --swc-color-loupe-drop-shadow-{x,y,blur,color} (×4)
  • --swc-color-loupe-inner-border-{color,width} (×2)
  • --swc-color-loupe-outer-border-width

The --swc-color-loupe-opacity / --swc-color-loupe-transform passthrough pair got replaced with literals on .swc-ColorLoupe, driven by :host([open]) .swc-ColorLoupe.

The outer-border-color is only modified in forced-colors mode, so per the exclusions guideline it's now an internal --_swc-color-loupe-outer-border-color shared by the normal rule and the forced-colors media query — same pattern status-light uses.

display: block;
position: absolute;
inset-block-end: calc((token("color-handle-size") - token("color-handle-outer-border-width")) + var(--swc-color-loupe-offset, token("color-loupe-bottom-to-color-handle")));
inset-inline-end: calc(50% - (token("color-loupe-width") / 2));
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.

Since color-loupe-width is referenced multiple times, you can hold this value in a private property.

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.

Done. Defined once as a private property on :host and referenced from all three sites:

:host {
  --_swc-color-loupe-width: token("color-loupe-width");
  ...
  inline-size: var(--_swc-color-loupe-width);
  inset-inline-end: calc(50% - (var(--_swc-color-loupe-width) / 2));
}

:host(:dir(rtl)) {
  inset-inline-end: calc(50% - (var(--_swc-color-loupe-width) / 2) - 1px);
}

<div class="swc-ColorLoupe-checkerboard swc-ColorLoupe--clipped"></div>
<div
class="swc-ColorLoupe-colorFill swc-ColorLoupe--clipped"
style="background: ${this.color}"
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.

The second example it provides of passing this into the value of a custom property vs straight to the background would be preferred, and is the pattern also exemplified in the Spectrum CSS solution.

Comment thread 2nd-gen/packages/swc/components/color-loupe/ColorLoupe.ts
Comment on lines +107 to +115
* ### Technical structure
*
* The component has no slots. All rendering is internal.
*
* #### Properties
*
* - **open**: Controls visibility with animated CSS transitions on `opacity` and `transform`
* - **color**: CSS color string displayed inside the loupe; supports any valid format
* including named colors, hex, `rgba()`, and `hsl()`
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.

This summary is against the established docs patterns, especially as the following stories are intended to describe and demonstrate the properties (and slots, if they were applicable).

The other issue is that having the "Properties" sub-heading breaks the table of contents link to the Properties table at the end.

I would advise removing this as essentially duplicate/unneeded information.

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.

agree, you can remove technical structure and properties sections. can you reference how the other components have documented their anatomy? this seems to be very different including that visual structure section

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.

Addressed across the anatomy rewrite and a broader stories consistency pass.

Anatomy JSDoc rewritten to match the established pattern used by asset, avatar, badge, divider, icon, progress-circle, and status-light — a short intro sentence plus a numbered list of visible parts:

/**
 * A color loupe consists of:
 *
 * 1. **Floating loupe element** - A teardrop-shaped container positioned above the interaction point, with an inner and outer border
 * 2. **Color preview** - Displays the currently picked color over an opacity checkerboard so transparency is visible
 */

The #### Properties sub-heading that was colliding with the auto-generated Properties table's TOC anchor is gone, and so are the ### Visual structure / ### Technical structure sections.

Broader stories consistency pass brought the rest of the file in line with the reference components:

  • Added a HELPERS section (mirrors badge.stories.ts) with a small labeledLoupe helper and a COLOR_FORMATS label/color mapping
  • Replaced the inline wrapper-div boilerplate across Colors / OpenAndClosedStates / ParentDrivenVisibility with .map() iteration over typed data (same shape as badge's Sizes story)
  • Trimmed the meta JSDoc and dropped broken cross-component links to not-yet-migrated color components
  • Added .storyName overrides for sentence-case display in the sidebar
  • Fixed the flexLayout parameter value (true'row-wrap'; the boolean form wasn't a valid case in the flex-layout decorator and was silently falling through to default, which is why multi-loupe stories were rendering stacked vertically)

Comment on lines +38 to +39
// TODO: Migrate opacity-checkerboard to 2nd gen and consume it here; checkerboard styling is currently hardcoded in color-loupe.css.
// Tracked in SWC-2029.
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.

nit:

Suggested change
// TODO: Migrate opacity-checkerboard to 2nd gen and consume it here; checkerboard styling is currently hardcoded in color-loupe.css.
// Tracked in SWC-2029.
/**
* @todo SWC-2029 - Migrate opacity-checkerboard to 2nd gen and consume it here;
* checkerboard styling is currently hardcoded in color-loupe.css.
*/

Copy link
Copy Markdown
Contributor Author

@blunteshwar blunteshwar Apr 22, 2026

Choose a reason for hiding this comment

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

Applied verbatim:

/**
 * @todo SWC-2029 - Migrate opacity-checkerboard to 2nd gen and consume it
 * here; checkerboard styling is currently hardcoded in color-loupe.css.
 */

* Default color value for a newly created color loupe.
* Semi-transparent red allows the opacity checkerboard to show through.
*/
export const COLOR_LOUPE_DEFAULT_COLOR = 'rgba(255, 0, 0, 0.5)';
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.

this isnt a type and can we defined in the base since its just a default value

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.

Good catch — you're right, it wasn't a type. Since COLOR_LOUPE_DEFAULT_COLOR was the only thing in ColorLoupe.types.ts and no associated enum/union justified its existence, I deleted the file entirely. The default now lives inline at the property declaration in the base class:

@property({ type: String })
public color = 'rgba(255, 0, 0, 0.5)';

A short line in the property JSDoc explains the choice (semi-transparent red reveals the opacity checkerboard on initial render).

Also updated the index.ts barrel to drop the types re-export, and the migration checklist to note the file is intentionally absent with a link back to this thread for future reference.

* transparency (which reveals the checkerboard behind).
*/
@property({ type: String })
public color = COLOR_LOUPE_DEFAULT_COLOR;
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.

We should be validating color is a valid CSS string and warn/error if its invalid. I could see a method similar to ifDefined that we can use across other components expecting CSS strings. isValidColoror isCSSColor something along those lines. could be established here or just go straight to core tools as a utility method.

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.

We might want to leave that to implementation to use their own validation to avoid any perf load and also they might accept a format that we don't include. Possibly a TODO to investigate later?

Copy link
Copy Markdown
Contributor

@caseyisonit caseyisonit Apr 22, 2026

Choose a reason for hiding this comment

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

when i say validating i mean string validation that it matches a color value structure. like does it match an rgba(0, 0, 0, 0) as a string for an example.

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.

We already have a method public validateColorString in ColorController.ts in reactive controllers. Since color-loupe always receives color from parent-component hence it does not use validateColorString method.

Copy link
Copy Markdown
Contributor Author

@blunteshwar blunteshwar Apr 22, 2026

Choose a reason for hiding this comment

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

ColorController.validateColorStringis the right hook if this ever needs standalone validation. Updated the @todo to reference the existing utility rather than proposing a new one, and made the reasoning explicit (the loupe delegates to its parent, which validates upstream):

/**
 * The CSS color value to display inside the loupe.
 * Supports any valid CSS color string, including those with alpha
 * transparency (which reveals the checkerboard behind).
 *
 * Default is semi-transparent red so the opacity checkerboard is visible
 * when the component is rendered without a `color` attribute.
 *
 * @todo Runtime validation is intentionally not performed here. The loupe
 * always receives its color from a parent color-picker component, which
 * validates upstream via `validateColorString` on `ColorController`. If
 * the loupe ever needs standalone validation (e.g. consumed outside a
 * parent color picker), reuse `ColorController.validateColorString`
 * rather than adding a separate utility.
 */

Comment on lines +223 to +257
## Inline CSS strings from component properties

Some components accept a property whose value is a CSS string — for example, `<swc-color-loupe>` exposes a `color` property that accepts any valid CSS color:

```ts
// ColorLoupe.ts
<div
class="swc-ColorLoupe-colorFill swc-ColorLoupe--clipped"
style="background: ${this.color}"
></div>
```

When interpolating a property directly into a `style` attribute, the value is forwarded verbatim to the browser's CSS parser. That means:

- **Only accept CSS strings from trusted sources.** Component properties that participate in an inline `style` (or a template string, or a `styleMap` entry) must be treated as trusted input. Do not expose such properties to arbitrary user-generated content without validation at the call site.
- **Prefer structured inputs wherever possible.** If a property has a small set of known-good values (variant, size, semantic color), drive styling through `classMap` and design tokens instead of a free-form CSS string.
- **Use a CSS custom property for the value, not a full declaration.** Set `--swc-<component>-<role>: ${value}` via `styleMap` rather than interpolating an entire declaration. This scopes what the property can affect to what the component's CSS explicitly consumes.
- **Document the contract on the property.** The property's JSDoc must state that the value is passed to the CSS parser as-is and that callers are responsible for ensuring it is a valid, trusted CSS value.

```ts
// ✅ Preferred — structured input via classMap
class=${classMap({
[`swc-Badge--${this.variant}`]: this.variant != null,
})}

// ✅ Acceptable — typed CSS property, scoped via a custom property
style=${styleMap({
'--swc-color-loupe-picked-color': this.color,
})}

// ⚠️ Use with care — full inline declaration interpolated from a property.
// Only do this when the component's API contract explicitly declares
// the property as a trusted CSS string and the JSDoc documents that.
style="background: ${this.color}"
```
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.

this section is confusing and seems a little contradictory. the example at the top should reflect the "acceptable" pattern. the class map shouldnt be included in this section since thats a class and not an inline style. the last one should actually be an example of a bad example and not to do.

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.

Reworked. Each of the three points addressed:

  1. Top example now reflects the acceptable pattern — the intro code block leads with the styleMap-plus-custom-property approach, followed by a CSS block showing the rule that consumes the property:

    style=${styleMap({
      '--swc-color-loupe-picked-color': this.color,
    })}
    .swc-ColorLoupe-colorFill {
      background: var(--swc-color-loupe-picked-color);
    }
  2. ClassMap example removed — that's a class concern, not an inline-style concern, so both the code example and the prose bullet referencing classMap are gone. The "prefer structured inputs" advice already lives in the dedicated "classMap patterns" section directly above this one.

  3. Last example is now ❌ Bad, not ⚠️ Use with care — plus a comment spelling out the actual cost: the full declaration is re-parsed on every render, and a malformed value can corrupt adjacent styles.

Section now has just two code examples: ✅ Good (styleMap + custom property) and ❌ Bad (interpolating into a full declaration).

Per review, validation delegation is already handled: the parent color-picker
validates upstream via ColorController.validateColorString before passing to
the loupe. Update the @todo to reference the existing utility rather than
proposing a new one.

Made-with: Cursor
Copy link
Copy Markdown
Contributor

@5t3ph 5t3ph left a comment

Choose a reason for hiding this comment

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

Noticed one last issue, but the other changes are great improvements!

Comment thread 2nd-gen/packages/swc/components/color-loupe/color-loupe.css
Co-authored-by: Stephanie Eckles <seckles@adobe.com>
@blunteshwar blunteshwar requested a review from 5t3ph April 22, 2026 17:04
Copy link
Copy Markdown
Contributor

@5t3ph 5t3ph left a comment

Choose a reason for hiding this comment

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

LGTM!

@blunteshwar blunteshwar merged commit 88e37da into color-loupe-migration Apr 22, 2026
26 checks passed
@blunteshwar blunteshwar deleted the claude-do-the-honors branch April 22, 2026 18:06
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Status:Ready for review PR ready for review or re-review.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants