UNPKG

@electrovir/color

Version:

A wrapper for culori with an extremely simple API.

119 lines (115 loc) 4.73 kB
/* 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.colorFormatName]: { ...inputs.color[inputs.colorFormatName], [inputs.colorCoordinateName]: 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}> `; }, });