color-elements
Version:
A set of web components for working with color. A Color.js project.
136 lines (118 loc) • 4.09 kB
CSS
:host {
display: block;
width: var(--size, 320px);
max-width: 100%;
/* Default ring thickness (fraction of the radius), used only when there's
no channel and the disc renders as a ring. */
--ring-thickness: 0.35;
/* Render every slotted point (a <color-swatch>, or a <color-scale>'s swatches)
as a compact dot with a hover/focus tooltip. Both properties inherit down to
the slotted elements, which style themselves accordingly — hue-wheel only
positions them (see ::slotted below and hue-wheel-global.css). */
--swatch-style: dot;
--details-style: compact;
}
#wheel {
position: relative;
aspect-ratio: 1;
container-type: inline-size;
border-radius: 50%;
cursor: crosshair;
/* Suppress pan/zoom so dragging on touch doesn't scroll the page. */
touch-action: none;
-webkit-user-select: none;
user-select: none;
}
:host([readonly]) #wheel {
cursor: default;
}
#disc {
position: absolute;
inset: 0;
border-radius: 50%;
/* The wheel is the only intended pointer target; let events fall through the
disc (and its layers) to #wheel. */
pointer-events: none;
}
/* Ring mode: punch a circular hole so only an annulus of hue shows. */
:host(:state(ring)) #disc {
--_inner: calc((1 - var(--ring-thickness)) * 50cqi);
mask: radial-gradient(circle, transparent 0 var(--_inner), #000 var(--_inner));
}
.layer {
position: absolute;
/* Outer edge maps to this layer's channel value: fraction 1 → full size,
fraction 0 → a point at the centre. */
inset: calc((1 - var(--fraction)) * 50%);
border-radius: 50%;
}
/* Polar placement, shared by the marker and slotted points. --hue (angle) +
--channel (raw value) come from JS. The point sits at angle --hue, --_r out from
the centre: go to the angle, move out, rotate back so it stays upright — no trig.
`translate` (applied before `transform` per spec order) centres the box on the
wheel centre first; `transform` then walks it out to the polar point. */
#marker,
::slotted(color-swatch) {
position: absolute;
top: 50%;
left: 50%;
margin: 0;
--_channel-progress: clamp(
0,
(var(--channel) - var(--channel-min)) / (var(--channel-max) - var(--channel-min)),
1
);
--_r: calc(var(--_channel-progress) * 50cqi);
translate: -50% -50%;
transform: rotate(calc(-1 * var(--hue))) translateX(var(--_r)) rotate(var(--hue));
}
/* The built-in marker: the current colour as a <color-swatch> dot (dot mode comes
from the inherited --swatch-style), distinguished from plotted points by the dot
knobs, which reproduce the prominent white ring. To customise it (incl. adding a
tooltip), slot your own <color-swatch slot="marker"> instead. A slotted marker
stacks above the points like this one does. */
#marker,
::slotted([slot="marker"]) {
z-index: 2;
}
/* No colour selected → no marker. A bare `hidden` can't hide a <color-swatch> (its
own :host sets `display`), so hide it explicitly; otherwise it parks at the centre
and jumps to the first click. */
#marker[hidden],
::slotted([slot="marker"][hidden]) {
display: none;
}
#marker {
--dot-size: 1em;
--dot-border: 2px solid white;
--dot-shadow: 0 0 0 1px rgb(0 0 0 / 0.4), 0 1px 2px rgb(0 0 0 / 0.4);
/* No tooltip by default — the dot just marks the current colour. */
&::part(details) {
display: none;
}
}
/* Plotted points: a directly-slotted <color-swatch> is positioned by its own
hue/channel (set by JS) — the same polar placement as the marker (see above).
::slotted() reaches a slotted swatch from here; a <color-scale>'s inner swatches
are ::part()s and can't be reached through ::slotted(), so those are placed in
hue-wheel-global.css. */
:host(:not([readonly])) {
#marker {
cursor: grab;
&:active {
cursor: grabbing;
}
}
}
/* No channel → marker and points ride the middle of the ring. */
:host(:state(ring)) :is(#marker, ::slotted(color-swatch)) {
--_r: calc((1 - var(--ring-thickness) / 2) * 50cqi);
}
#marker:focus-visible,
::slotted([slot="marker"]:focus-visible) {
outline: 2px solid Highlight;
outline-offset: 2px;
}
slot {
display: block;
}