Skip to content

Swatch picker#4

Open
mttshw wants to merge 28 commits intokeithamus:mainfrom
mttshw:swatch-picker
Open

Swatch picker#4
mttshw wants to merge 28 commits intokeithamus:mainfrom
mttshw:swatch-picker

Conversation

@mttshw
Copy link

@mttshw mttshw commented Dec 12, 2025

No description provided.

Copy link
Owner

@keithamus keithamus left a comment

Choose a reason for hiding this comment

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

Looking great! Some notes:

mttshw and others added 12 commits December 13, 2025 10:35
Co-authored-by: Keith Cirkel <keithamus@users.noreply.github.com>
Co-authored-by: Keith Cirkel <keithamus@users.noreply.github.com>
Co-authored-by: Keith Cirkel <keithamus@users.noreply.github.com>
Co-authored-by: Keith Cirkel <keithamus@users.noreply.github.com>
Co-authored-by: Keith Cirkel <keithamus@users.noreply.github.com>
mttshw and others added 10 commits December 14, 2025 14:35
Co-authored-by: Keith Cirkel <keithamus@users.noreply.github.com>
Co-authored-by: Keith Cirkel <keithamus@users.noreply.github.com>
Co-authored-by: Keith Cirkel <keithamus@users.noreply.github.com>
Co-authored-by: Keith Cirkel <keithamus@users.noreply.github.com>
mttshw and others added 2 commits December 15, 2025 09:41
Co-authored-by: Keith Cirkel <keithamus@users.noreply.github.com>
Copy link
Owner

@keithamus keithamus left a comment

Choose a reason for hiding this comment

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

This is looking really good but I'd recommend some refinements.

One general comment is that there's a mix of whitespace. All of these files should probably use tabs for indentation, perhaps we should install prettier?

Also index.html doesn't link to this page at all, so that would be really nice!

<h3>Usage</h3>
<pre>
<code>
&lt;open-swatch-picker&gt;&lt;/open-swatch-picker&gt;
Copy link
Owner

Choose a reason for hiding this comment

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

Tiny nit but you could get away with just &lt; here and others.

Suggested change
&lt;open-swatch-picker&gt;&lt;/open-swatch-picker&gt;
&lt;open-swatch-picker>&lt;/open-swatch-picker>

Comment on lines +123 to +126
if (this.#value !== newValue) {
this.#value = newValue;
this.onValueChange(newValue); // Call the watcher function
}
Copy link
Owner

Choose a reason for hiding this comment

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

Having this guard prevents the picker from picking the same color twice. STR:

  1. Open the page
  2. Click "Choose color"
  3. Click on the first button
  4. Observe dialog is closed, button is populated.
  5. Click the button to open the dialog again
  6. Click the first button (i.e. same button as first choice)
  7. Observe the dialog does not close. Only choice is to pick another color or press Escape.

Comment on lines +133 to +135
get hideOutputLabel() {
return this.getAttribute('hide-output-label');
}
Copy link
Owner

Choose a reason for hiding this comment

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

Perhaps output-label="" is a more ergonomic way to handle this? As in <open-swatch-picker output-label="">

Perhaps we can do more interest things with a string attribute later on?

(This is non blocking, just a note).

<main>
${scales.map(scale=>makeScale(scale)).join('')}
</main>
</dialog>
Copy link
Owner

Choose a reason for hiding this comment

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

This dialog has no label, making it a little tricky for screen readers to navigate. Also main is redundant. Some options:

  1. Add an aria-label, offers little customisation of the label but also the simplest:
Suggested change
</dialog>
<dialog closedby="any" id="select-color-dialog" aria-label="Color Picker">
${scales.map(scale=>makeScale(scale)).join('')}
</dialog>
  1. Add a named slot so consumers can customise:
Suggested change
</dialog>
<dialog closedby="any" id="select-color-dialog">
<slot name="header" part="dialog-header"><h1>Color picker</h1></slot>
${scales.map(scale=>makeScale(scale)).join('')}
</dialog>

}

get outputType() {
return this.getAttribute('output-type');
Copy link
Owner

Choose a reason for hiding this comment

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

This has no validation which is a little awkward. I'd prefer something like:

Suggested change
return this.getAttribute('output-type');
const type = this.getAttribute('output-type');
if (type == 'hex' || type == 'rgb' || type == 'oklch' || type == 'var') return type;
return 'var';

This way you can check in JS if something went wrong by setting the value and reading it back.

}
}

get outputType() {
Copy link
Owner

Choose a reason for hiding this comment

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

This should also have a setter:

Suggested change
get outputType() {
set outputType(type) {
this.setAttribute('output-type', type);
}
get outputType() {

Comment on lines +152 to +156
let outputVal = this.value;

if(this.outputType === 'hex') outputVal = this.hexValue;
if(this.outputType === 'rgb') outputVal = this.rgbValue;
if(this.outputType === 'oklch') outputVal = this.oklchValue;
Copy link
Owner

Choose a reason for hiding this comment

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

Having var(--emerald-6) is nice but perhaps it's also nice to just get the ident without var(), so you can for e.g. make your own var(--emerald-6, var(--fallback)) or similar.

So perhaps a new output type of 'ident' would be useful here.

This is non blocking.

shadowRoot = this.attachShadow({ mode: "open" });

#value = '';
dialog = null;
Copy link
Owner

Choose a reason for hiding this comment

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

This could be a getter, and it probably should be private:

Suggested change
dialog = null;
get #dialog() { return this.shadowRoot.querySelector('dialog') }


this.shadowRoot
.querySelector('span[part="label"]')
.textContent = this.getAttribute('label') || 'Choose Color';
Copy link
Owner

Choose a reason for hiding this comment

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

label is not reactive, so if I change it, the shadowRoot is not updated. I'd recommend something like:

export class OpenSwatchPicker extends HTMLElement {
	static observedAttributes = ['label']

	//...

	#updateValue() {
		this.shadowRoot
			.querySelector('span[part="label"]')
			.textContent = this.getAttribute('label') || 'Choose Color';
 }

	attributeChangedCallback(name, oldValue, newValue) {
		if (name == 'value') this.#updateValue()
	}

	connectedCallback() {
		//...
 		this.#updateValue()
		//...
	}
}

The same is also likely true for things like output-type, which might have some nice emergent properties if that was reactive.

Comment on lines +193 to +196
const swatchStyle = getComputedStyle(e.target);
const swatchColor = swatchStyle.backgroundColor;

this.oklchValue = swatchColor;
Copy link
Owner

Choose a reason for hiding this comment

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

Nit, this is probably as readable, but either way is fine:

Suggested change
const swatchStyle = getComputedStyle(e.target);
const swatchColor = swatchStyle.backgroundColor;
this.oklchValue = swatchColor;
this.oklchValue = getComputedStyle(e.target).backgroundColor;

}

updateFormControls() {
for (const el of this.getRootNode().querySelectorAll('input[open-swatch-picker],output[for]')) {
Copy link
Owner

Choose a reason for hiding this comment

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

Might be nice to handle inputs too:

Suggested change
for (const el of this.getRootNode().querySelectorAll('input[open-swatch-picker],output[for]')) {
for (const el of this.getRootNode().querySelectorAll('input[open-swatch-picker],input[for],output[for]')) {

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.

2 participants