diff --git a/src/channel-picker/README.md b/src/channel-picker/README.md
index a8d3ce51..b6c1bdcf 100644
--- a/src/channel-picker/README.md
+++ b/src/channel-picker/README.md
@@ -15,6 +15,12 @@ the default `oklch.l` will be used:
```
+If you need a more compact version of the picker, add the `compact` boolean attribute to get one:
+
+```html
+
+```
+
You can hide the `color-space` part with CSS to show only the coordinates of the specified space:
```html
@@ -46,6 +52,13 @@ All properties are reactive and can be set programmatically:
```
+```html
+
+ Compact picker
+
+
+```
+
`` plays nicely with other color elements, like [``](../channel-slider):
```html
@@ -64,7 +77,7 @@ All properties are reactive and can be set programmatically:
channel_slider.space = space;
channel_slider.channel = channel;
}
-
+
channel_picker.onvaluechange = updateSlider;
```
@@ -76,6 +89,7 @@ All properties are reactive and can be set programmatically:
| Attribute | Property | Property type | Default value | Description |
|-----------|----------|---------------|---------------|----------------------------------|
| `value` | `value` | `string` | `oklch.l` | The current value of the picker. |
+| `compact` | `compact` | `boolean` | `false` | Whether the picker should be rendered compact or not. |
### Getters
@@ -100,4 +114,5 @@ These properties are read-only.
|----------------|------------------------------------------------------|
| `color-space` | The internal [``](../space-picker/) element. |
| `color-space-base` | The internal `` element of [``](../space-picker/). |
-| `color-channel-base` | The internal `` element. |
+| `color-channel-base` | If the picker is compact, the internal `` element used to render the channels. |
+| `channels` | The container that wraps the current color space channels. |
diff --git a/src/channel-picker/channel-picker.css b/src/channel-picker/channel-picker.css
index 0d4d341c..487b0384 100644
--- a/src/channel-picker/channel-picker.css
+++ b/src/channel-picker/channel-picker.css
@@ -1,31 +1,148 @@
:host {
- --border-width: 1px;
- --border-color: rgb(0 0 0 / .2);
- --border-radius: .2em;
+ --_color-neutral: var(--color-neutral, oklch(50% 0.03 230));
+ --_color-neutral-5a: color-mix(in oklch, var(--_color-neutral) 5%, canvas);
+ --_color-neutral-10a: color-mix(in oklch, var(--_color-neutral) 10%, canvas);
+ --_color-neutral-15a: color-mix(in oklch, var(--_color-neutral) 15%, canvas);
+ --_color-neutral-20a: color-mix(in oklch, var(--_color-neutral) 20%, canvas);
+ --_color-neutral-30a: color-mix(in oklch, var(--_color-neutral) 30%, canvas);
+ --_color-neutral-40a: color-mix(in oklch, var(--_color-neutral) 40%, canvas);
+ --_color-neutral-50a: color-mix(in oklch, var(--_color-neutral) 50%, canvas);
+ --_color-neutral-70a: color-mix(in oklch, var(--_color-neutral) 70%, canvas);
+ --_color-neutral-90a: color-mix(in oklch, var(--_color-neutral) 90%, canvas);
- padding: .3em .5em;
+ --_accent-color: var(--accent-color, var(--_color-neutral-15a));
- border: var(--border-width) solid var(--border-color);
- border-radius: var(--border-radius);
+ --_selected-channel-background: var(--selected-channel-background, var(--_color-neutral-15a));
+ --_selected-channel-shadow: 0 .1em .2em var(--_color-neutral-40a) inset, 0 0 0 2em var(--_color-neutral-20a) inset;
+
+ --_border-color: var(--border-color, var(--_color-neutral-30a));
+ --_border-width: var(--border-width, 1px);
+ --_border-radius: var(--border-radius, .2em);
+
+ --_gap: 1.5rem;
+
+ @supports (color: light-dark(white, black)) {
+ --_accent-color-light: var(--accent-color-light, var(--_color-neutral-5a));
+ --_accent-color-dark: var(--accent-color-dark, var(--_color-neutral-20a));
+ --_accent-color: light-dark(var(--_accent-color-light), var(--_accent-color-dark));
+
+ --_selected-channel-background-light: var(--selected-channel-background-light, var(--_color-neutral-5a));
+ --_selected-channel-background-dark: var(--selected-channel-background-dark, var(--_color-neutral-20a));
+ --_selected-channel-background: light-dark(var(--_selected-channel-background-light), var(--_selected-channel-background-dark));
+
+ --_selected-channel-shadow: 0 .1em .2em light-dark(var(--_color-neutral-20a), var(--_color-neutral-50a)) inset, 0 0 0 2em light-dark(var(--_color-neutral-10a), var(--_color-neutral-40a)) inset;
+
+ --_border-color-light: var(--border-color-light, var(--_color-neutral-30a));
+ --_border-color-dark: var(--border-color-dark, var(--_color-neutral-70a));
+ --_border-color: light-dark(var(--_border-color-light), var(--_border-color-dark));
+ }
+
+ display: flex;
+ gap: var(--_gap);
+ align-items: center;
+ inline-size: fit-content;
+
+ [part="color-space"] {
+ position: relative;
+
+ border-color: var(--_border-color);
+ font-size: 120%;
+
+ &::before,
+ &::after {
+ --_line-height: calc(1.6 * var(--_border-width));
+ position: absolute;
+ content: "";
+ }
+
+ /* Line */
+ &::before {
+ inset-block-start: calc(50% - var(--_line-height) / 2);
+ inset-inline-start: calc(100% + var(--_border-width));
+ inline-size: var(--_gap);
+ block-size: var(--_line-height);
+ background-color: var(--_border-color);
+ }
+
+ /* Circle */
+ &::after {
+ --_radius: .12em;
+
+ inset-block-start: calc(50% - var(--_radius));
+ inset-inline-start: calc(100% - var(--_border-width) / 4 + var(--_gap) - var(--_radius) / 2);
+ border-radius: 50%;
+ block-size: calc(2 * var(--_radius));
+ aspect-ratio: 1;
+ background-color: var(--_color-neutral-50a);
+ background-color: light-dark(var(--_color-neutral-30a), var(--_color-neutral-90a));
+ }
+ }
+
+ #channels {
+ font-size: 75%;
+
+ color: inherit;
+ background: inherit;
+ }
}
-#picker {
- font: inherit;
- color: inherit;
- background: inherit;
- border: none;
- field-sizing: content;
- cursor: pointer;
+:host(:is(.compact, [compact])) {
+ [part="color-channel-base"] {
+ padding-block: .65em;
+ padding-inline: .5em .4em;
+
+ border: var(--_border-width) solid var(--_border-color);
+ border-radius: var(--_border-radius);
- &:focus:not(:focus-visible) {
- outline: none;
+ font: inherit;
+
+ field-sizing: content;
+
+ cursor: pointer;
}
}
-#space_picker {
- padding: initial;
- padding-inline-end: .4em;
- border-radius: 0;
- border: none;
- border-inline-end: var(--border-width) solid var(--border-color);
+:host(:not(.compact, [compact])) {
+ #channels {
+ display: flex;
+ inline-size: fit-content;
+
+ border: var(--_border-width) solid var(--_border-color);
+ border-radius: var(--_border-radius);
+
+ label {
+ display: grid;
+ place-items: center;
+ min-inline-size: 5ch;
+ padding: .3em 1.2em;
+
+ cursor: pointer;
+
+ &:hover {
+ background-color: var(--_accent-color);
+ }
+
+ & + & {
+ border-inline-start: var(--_border-width) solid var(--_border-color);
+ }
+
+ &:has(:checked) {
+ background: var(--_selected-channel-background);
+ box-shadow: var(--_selected-channel-shadow);
+ }
+ }
+
+ /* Visually hide the radio buttons */
+ .sr-only {
+ position: absolute;
+ width: 1px;
+ height: 1px;
+ padding: 0;
+ margin: -1px;
+ overflow: hidden;
+ clip: rect(0, 0, 0, 0);
+ white-space: nowrap;
+ border-width: 0;
+ }
+ }
}
diff --git a/src/channel-picker/channel-picker.js b/src/channel-picker/channel-picker.js
index 6ab8a058..4b7dbd21 100644
--- a/src/channel-picker/channel-picker.js
+++ b/src/channel-picker/channel-picker.js
@@ -8,7 +8,8 @@ const Self = class ChannelPicker extends ColorElement {
static shadowStyle = true;
static shadowTemplate = `
- `;
+
+ `;
constructor () {
super();
@@ -25,14 +26,14 @@ const Self = class ChannelPicker extends ColorElement {
connectedCallback () {
super.connectedCallback?.();
- this._el.picker.addEventListener("input", this);
+ this._el.channels.addEventListener("input", this);
}
disconnectedCallback () {
super.disconnectedCallback?.();
this._el.space_picker.removeEventListener("spacechange", this);
- this._el.picker.removeEventListener("input", this);
+ this._el.channels.removeEventListener("input", this);
}
get selectedSpace () {
@@ -40,7 +41,22 @@ const Self = class ChannelPicker extends ColorElement {
}
get selectedChannel () {
- return this.selectedSpace.coords?.[this._el.picker.value];
+ return this.selectedSpace.coords?.[this.compact ? this.#channelSelect.value : this.#checkedChannel];
+ }
+
+ get #checkedChannel () {
+ return this._el.channels.querySelector("input[type=radio][name=channel]:checked")?.value;
+ }
+
+ set #checkedChannel (value) {
+ let input = this._el.channels.querySelector(`input[type=radio][name=channel][value="${ value }"]`);
+ if (input) {
+ input.checked = true;
+ }
+ }
+
+ get #channelSelect () {
+ return this._el.channels.querySelector("select[part=color-channel-base]");
}
/**
@@ -58,9 +74,24 @@ const Self = class ChannelPicker extends ColorElement {
return;
}
- this._el.picker.innerHTML = Object.entries(coords)
- .map(([id, coord]) => `${ coord.name } `)
- .join("\n");
+ let compact = this.compact;
+ this.classList.toggle("compact", compact);
+
+ let html = compact ? [``] : [];
+ html.push(...Object.entries(coords)
+ // By default, the first channel is selected
+ .map(([id, coord], index) => {
+ if (compact) {
+ return `${ coord.name } `;
+ }
+ else {
+ return ` ${ coord.name } `;
+ }
+ }));
+ if (compact) {
+ html.push(" ");
+ }
+ this._el.channels.innerHTML = html.join("\n");
let [prevSpace, prevChannel] = this.value?.split(".") ?? [];
if (prevSpace && prevChannel) {
@@ -68,11 +99,21 @@ const Self = class ChannelPicker extends ColorElement {
let currentChannelName = coords[prevChannel]?.name;
if (prevChannelName === currentChannelName) {
// Preserve the channel if it exists in the new space and has the same name ("b" in "oklab" is not the same as "b" in "p3")
- this._el.picker.value = prevChannel;
+ if (compact) {
+ this.#channelSelect.value = prevChannel;
+ }
+ else {
+ this.#checkedChannel = prevChannel;
+ }
}
else if (this.#history?.[space.id]) {
// Otherwise, try to restore the last channel used
- this._el.picker.value = this.#history[space.id];
+ if (compact) {
+ this.#channelSelect.value = this.#history[space.id];
+ }
+ else {
+ this.#checkedChannel = this.#history[space.id];
+ }
}
}
}
@@ -82,8 +123,10 @@ const Self = class ChannelPicker extends ColorElement {
this.#render();
}
- if ([this._el.space_picker, this._el.picker].includes(event.target)) {
- let value = `${ this._el.space_picker.value }.${ this._el.picker.value }`;
+ if (this._el.space_picker === event.target || this.#channelSelect === event.target || event.target.matches("input[type=radio]")) {
+ let space = this._el.space_picker.value;
+ let channel = this.compact ? this.#channelSelect.value : this.#checkedChannel;
+ let value = `${ space }.${ channel }`;
if (value !== this.value) {
this.value = value;
}
@@ -95,7 +138,7 @@ const Self = class ChannelPicker extends ColorElement {
let [space, channel] = (this.value + "").split(".");
let currentSpace = this._el.space_picker.value;
- let currentCoord = this._el.picker.value;
+ let currentCoord = this.compact ? this.#channelSelect?.value : this.#checkedChannel;
let currentValue = `${ currentSpace }.${ currentCoord }`;
if (!space || !channel) {
@@ -118,7 +161,12 @@ const Self = class ChannelPicker extends ColorElement {
let coords = Object.keys(this.selectedSpace.coords ?? {});
if (coords.includes(channel)) {
- this._el.picker.value = channel;
+ if (this.compact) {
+ this.#channelSelect.value = channel;
+ }
+ else {
+ this.#checkedChannel = channel;
+ }
}
else {
currentCoord = coords.includes(currentCoord) ? currentCoord : coords[0];
@@ -138,23 +186,44 @@ const Self = class ChannelPicker extends ColorElement {
}
}
}
+
+ if (name === "compact") {
+ this.#render();
+ }
}
static props = {
value: {
default: "oklch.l",
},
+
+ compact: {
+ default: false,
+ parse (value) {
+ if (value === undefined || value === null || value === false || value === "false") {
+ return false;
+ }
+
+ if (value === "" || value === "compact" || value === true || value === "true") {
+ // Boolean attribute
+ return true;
+ }
+ },
+ reflect: {
+ from: true,
+ },
+ },
};
static events = {
change: {
from () {
- return [this._el.space_picker, this._el.picker];
+ return [this._el.space_picker, this._el.channels];
},
},
input: {
from () {
- return [this._el.space_picker, this._el.picker];
+ return [this._el.space_picker, this._el.channels];
},
},
valuechange: {