@electrovir/color
Version:
A wrapper for culori with an extremely simple API.
119 lines (115 loc) • 4.73 kB
JavaScript
/* node:coverage disable */
import { assert, assertWrap } from '@augment-vir/assert';
import { createArray } from '@augment-vir/common';
import { extractEventTarget } from '@augment-vir/web';
import { css, defineElement, defineElementEvent, html, listen, onDomRendered, unsafeCSS, } from 'element-vir';
import { ViraInput } from 'vira/dist/elements/vira-input.element.js';
import { viraFontCssVars } from 'vira/dist/styles/font.js';
import { colorFormats, } from '../data/color-class/color-formats.js';
import { Color } from '../data/color-class/color.js';
/**
* A slider for a specific color coordinate in a specific color space in a specific color.
*
* @category Elements
*/
export const VirColorSlider = defineElement()({
tagName: 'vir-color-slider',
cssVars: {
'vir-color-slider-gradient': 'black',
},
styles: ({ cssVars }) => css `
:host {
display: flex;
align-items: center;
font-family: ${viraFontCssVars['vira-monospace'].value};
gap: 2px;
}
input[type='range'] {
flex-grow: 1;
appearance: none;
background: ${cssVars['vir-color-slider-gradient'].value};
height: 9px;
border-radius: 4px;
cursor: pointer;
}
${ViraInput} {
width: 76px;
}
.coordinate {
font-size: 18px;
margin-top: -4px;
}
`,
events: {
valueChange: defineElementEvent(),
},
render({ inputs, events, dispatch, cssVars }) {
const formatDefinition = colorFormats[inputs.colorFormatName];
const coordinateDefinition = formatDefinition.coords[inputs.colorCoordinateName];
if (!coordinateDefinition) {
throw new Error(`Invalid color coordinate '${inputs.colorCoordinateName}' for color format '${inputs.colorFormatName}'`);
}
const totalStops = 10;
const colorStops = createArray(totalStops, (index) => {
const value = coordinateDefinition.min +
(coordinateDefinition.max - coordinateDefinition.min) * (index / totalStops);
const stopColor = new Color({
[]: {
...inputs.color[inputs.colorFormatName],
[]: value,
},
});
return stopColor.toCss()[formatDefinition.conversionFormat];
});
const gradient = css `linear-gradient(to right, ${unsafeCSS(colorStops.join(','))})`;
const coordinateValue = assertWrap.isNumber(inputs.color[inputs.colorFormatName][inputs.colorCoordinateName]);
const displayValue = coordinateDefinition.radix
? Math.round(coordinateValue)
.toString(coordinateDefinition.radix)
.toUpperCase()
.padStart(coordinateDefinition.radixPad || 0, '0')
: String(coordinateValue);
return html `
<span class="coordinate">${inputs.colorCoordinateName.toUpperCase()}</span>
<input
type="range"
style=${css `
${cssVars['vir-color-slider-gradient'].name}: ${gradient};
`}
step=${Math.pow(10, coordinateDefinition.digits ? -coordinateDefinition.digits : 0)}
${onDomRendered((element) => {
/**
* We must set these imperatively to force the input element to match the
* inputs, otherwise when swapping color formats these color sliders don't
* update correctly.
*/
assert.instanceOf(element, HTMLInputElement);
element.min = String(coordinateDefinition.min);
element.max = String(coordinateDefinition.max);
element.value = String(coordinateValue);
})}
${listen('input', (event) => {
const element = extractEventTarget(event, HTMLInputElement);
const newValue = Number(element.value);
if (isNaN(newValue)) {
return;
}
dispatch(new events.valueChange(newValue));
})}
/>
<${ViraInput.assign({
value: displayValue,
})}
${listen(ViraInput.events.valueChange, (event) => {
const newValue = coordinateDefinition.radix
? parseInt(event.detail, coordinateDefinition.radix)
: Number(event.detail);
if (isNaN(newValue)) {
return;
}
dispatch(new events.valueChange(newValue));
})}
></${ViraInput}>
`;
},
});