colorpicker: add hwb colors

This commit is contained in:
tophf 2022-04-01 16:38:52 +03:00
parent f54d145bf5
commit 537372dffa
4 changed files with 225 additions and 255 deletions

View File

@ -2,29 +2,82 @@
const colorConverter = (() => {
const RXS_NUM = /\s*([+-]?(?:\d+\.?\d*|\d*\.\d+))(?:e[+-]?\d+)?/.source;
const RXS_NUM_ANGLE = `${RXS_NUM}(deg|g?rad|turn)?`;
const RX_COLOR = {
hex: /#([a-f\d]{3}(?:[a-f\d](?:[a-f\d]{2}){0,2})?)\b/iy,
hsl: new RegExp([
// num_or_angle, pct, pct [ , num_or_pct]?
`^(${RXS_NUM_ANGLE})\\s*,(${RXS_NUM}%\\s*(,|$)){2}(${RXS_NUM}%?)?\\s*$`,
// num_or_angle pct pct [ / num_or_pct]?
`^(${RXS_NUM_ANGLE})\\s+(${RXS_NUM}%\\s*(\\s|$)){2}(/${RXS_NUM}%?)?\\s*$`,
].join('|'), 'iy'),
hwb: new RegExp(
// num|angle|none pct|none pct|none [ / num|pct|none ]?
`^(${RXS_NUM_ANGLE}|none)(\\s+(${RXS_NUM}%|none)){2}(\\s+|$)(/${RXS_NUM}%?|none)?\\s*$`,
'iy'),
rgb: new RegExp([
// num, num, num [ , num_or_pct]?
// pct, pct, pct [ , num_or_pct]?
`^((${RXS_NUM}\\s*(,|$)){3}|(${RXS_NUM}%\\s*(,|$)){3})(${RXS_NUM}%?)?\\s*$`,
// num num num [ / num_or_pct]?
// pct pct pct [ / num_or_pct]?
`^((${RXS_NUM}\\s*(\\s|$)){3}|(${RXS_NUM}%\\s*(\\s|$)){3})(/${RXS_NUM}%?)?\\s*$`,
].join('|'), 'iy'),
};
const ANGLE_TO_DEG = {
grad: 360 / 400,
rad: 180 / Math.PI,
turn: 360,
};
const TO_HSV = {
hex: RGBtoHSV,
hsl: HSLtoHSV,
hwb: HWBtoHSV,
rgb: RGBtoHSV,
};
const FROM_HSV = {
hex: HSVtoRGB,
hsl: HSVtoHSL,
hwb: HSVtoHWB,
rgb: HSVtoRGB,
};
const guessType = c =>
'r' in c ? 'rgb' :
'w' in c ? 'hwb' :
'v' in c ? 'hsv' :
'l' in c ? 'hsl' :
undefined;
return {
parse,
format,
formatAlpha,
RGBtoHSV,
HSVtoRGB,
HSLtoHSV,
HSVtoHSL,
fromHSV: (color, type) => FROM_HSV[type](color),
toHSV: color => TO_HSV[color.type || 'rgb'](color),
constrain,
constrainHue,
guessType,
snapToInt,
testAt,
ALPHA_DIGITS: 3,
RX_COLOR,
// NAMED_COLORS is added below
};
function format(color = '', type = color.type, hexUppercase, usoMode) {
function format(color = '', type = color.type, {hexUppercase, usoMode, round} = {}) {
if (!color || !type) return typeof color === 'string' ? color : '';
const {a} = color;
let aStr = formatAlpha(a);
if (aStr) aStr = ', ' + aStr;
if (type !== 'hsl' && color.type === 'hsl') {
color = HSVtoRGB(HSLtoHSV(color));
}
const {r, g, b, h, s, l} = color;
const {a, type: src = guessType(color)} = color;
const aFmt = formatAlpha(a);
const aStr = aFmt ? ', ' + aFmt : '';
const srcConv = src === 'hex' ? 'rgb' : src;
const dstConv = type === 'hex' ? 'rgb' : type;
color = srcConv === dstConv ? color : FROM_HSV[dstConv](TO_HSV[srcConv](color));
round = round ? Math.round : v => v;
const {r, g, b, h, s, l, w} = color;
switch (type) {
case 'hex': {
let res = '#' + hex2(r) + hex2(g) + hex2(b) + (aStr ? hex2(Math.round(a * 255)) : '');
@ -36,51 +89,15 @@ const colorConverter = (() => {
return usoMode ? rgb : `rgb${aStr ? 'a' : ''}(${rgb}${aStr})`;
}
case 'hsl':
return `hsl${aStr ? 'a' : ''}(${h}, ${s}%, ${l}%${aStr})`;
return `hsl${aStr ? 'a' : ''}(${round(h)}, ${round(s)}%, ${round(l)}%${aStr})`;
case 'hwb':
return `hwb(${round(h)} ${round(w)}% ${round(b)}%${aFmt ? ' / ' + aFmt : ''})`;
}
}
// Copied from _hexcolor() in parserlib.js
function validateHex(color) {
return /^#[a-f\d]+$/i.test(color) && [4, 5, 7, 9].some(n => color.length === n);
}
function validateRGB(nums) {
const isPercentage = nums[0].endsWith('%');
const valid = isPercentage ? validatePercentage : validateNum;
return nums.slice(0, 3).every(valid);
}
function validatePercentage(s) {
if (!s.endsWith('%')) return false;
const n = Number(s.slice(0, -1));
return n >= 0 && n <= 100;
}
function validateNum(s) {
const n = Number(s);
return n >= 0 && n <= 255;
}
function validateHSL(nums) {
return validateAngle(nums[0]) && nums.slice(1, 3).every(validatePercentage);
}
function validateAngle(s) {
return /^-?(\d+|\d*\.\d+)(deg|grad|rad|turn)?$/i.test(s);
}
function validateAlpha(alpha) {
if (alpha.endsWith('%')) {
return validatePercentage(alpha);
}
const n = Number(alpha);
return n >= 0 && n <= 1;
}
function parse(str) {
if (typeof str !== 'string') return;
str = str.trim();
str = str.trim().toLowerCase();
if (!str) return;
if (str[0] !== '#' && !str.includes('(')) {
@ -90,47 +107,45 @@ const colorConverter = (() => {
}
if (str[0] === '#') {
if (!validateHex(str)) {
return null;
if (!testAt(RX_COLOR.hex, 0, str)) {
return;
}
str = str.slice(1);
const [r, g, b, a = 255] = str.length <= 4 ?
str.match(/(.)/g).map(c => parseInt(c + c, 16)) :
str.match(/(..)/g).map(c => parseInt(c, 16));
return {type: 'hex', r, g, b, a: a === 255 ? undefined : a / 255};
return {
type: 'hex',
r,
g,
b,
a: a === 255 ? undefined : a / 255,
};
}
const [, type, value] = str.match(/^(rgb|hsl)a?\((.*?)\)|$/i);
if (!type) return;
const comma = value.includes(',') && !value.includes('/');
const num = value.trim().split(comma ? /\s*,\s*/ : /\s+(?!\/)|\s*\/\s*/);
if (num.length < 3 || num.length > 4) return;
if (num[3] && !validateAlpha(num[3])) return null;
let a = !num[3] ? 1 : parseFloat(num[3]) / (num[3].endsWith('%') ? 100 : 1);
if (isNaN(a)) a = 1;
const first = num[0];
if (/rgb/i.test(type)) {
if (!validateRGB(num)) {
return null;
const [, func, type = func, value] = str.match(/^((rgb|hsl)a?|hwb)\(\s*(.*?)\s*\)|$/);
if (!func || !testAt(RX_COLOR[type], 0, value)) {
return;
}
const k = first.endsWith('%') ? 2.55 : 1;
const [r, g, b] = num.map(s => Math.round(parseFloat(s) * k));
return {type: 'rgb', r, g, b, a};
} else {
if (!validateHSL(num)) {
return null;
}
let h = parseFloat(first);
if (first.endsWith('grad')) h *= 360 / 400;
else if (first.endsWith('rad')) h *= 180 / Math.PI;
else if (first.endsWith('turn')) h *= 360;
const s = parseFloat(num[1]);
const l = parseFloat(num[2]);
return {type: 'hsl', h, s, l, a};
const [s1, s2, s3, sA] = value.split(/\s*[,/]\s*|\s+/);
const a = isNaN(sA) ? 1 : constrain(0, 1, sA / (sA.endsWith('%') ? 100 : 1));
if (type === 'rgb') {
const k = s1.endsWith('%') ? 2.55 : 1;
return {
type,
r: constrain(0, 255, Math.round(s1 * k)),
g: constrain(0, 255, Math.round(s2 * k)),
b: constrain(0, 255, Math.round(s3 * k)),
a,
};
}
const h = constrainHue(parseFloat(s1) * (ANGLE_TO_DEG[s1.match(/\D*$/)[0]] || 1));
const n2 = constrain(0, 100, parseFloat(s2) || 0);
const n3 = constrain(0, 100, parseFloat(s3) || 0);
return type === 'hwb'
? {type, h, w: n2, b: n3, a}
: {type, h, s: n2, l: n3, a};
}
function formatAlpha(a) {
@ -164,8 +179,8 @@ const colorConverter = (() => {
};
}
function HSVtoRGB({h, s, v}) {
h = constrainHue(h) % 360;
function HSVtoRGB({h, s, v, a}) {
h = constrainHue(h);
const C = s * v;
const X = C * (1 - Math.abs((h / 60) % 2 - 1));
const m = v - C;
@ -180,9 +195,11 @@ const colorConverter = (() => {
r: snapToInt(Math.round((r + m) * 255)),
g: snapToInt(Math.round((g + m) * 255)),
b: snapToInt(Math.round((b + m) * 255)),
a,
};
}
function HSLtoHSV({h, s, l, a}) {
const t = s * (l < 50 ? l : 100 - l) / 100;
return {
@ -193,16 +210,41 @@ const colorConverter = (() => {
};
}
function HSVtoHSL({h, s, v}) {
function HSVtoHSL({h, s, v, a}) {
const l = (2 - s) * v / 2;
const t = l < .5 ? l * 2 : 2 - l * 2;
return {
h: Math.round(constrainHue(h)),
s: Math.round(t ? s * v / t * 100 : 0),
l: Math.round(l * 100),
h: constrainHue(h),
s: t ? s * v / t * 100 : 0,
l: l * 100,
a,
};
}
function HWBtoHSV({h, w, b, a}) {
w = constrain(0, 100, w) / 100;
b = constrain(0, 100, b) / 100;
return {
h: constrainHue(h),
s: b === 1 ? 0 : 1 - w / (1 - b),
v: 1 - b,
a,
};
}
function HSVtoHWB({h, s, v, a}) {
return {
h: constrainHue(h),
w: (1 - s) * v * 100,
b: (1 - v) * 100,
a,
};
}
function constrain(min, max, value) {
return value < min ? min : value > max ? max : value;
}
function constrainHue(h) {
return h < 0 ? h % 360 + 360 :
h > 360 ? h % 360 :
@ -215,7 +257,13 @@ const colorConverter = (() => {
}
function hex2(val) {
return (val < 16 ? '0' : '') + (val >> 0).toString(16);
return (val < 16 ? '0' : '') + Math.round(val).toString(16);
}
function testAt(rx, index, text) {
if (!rx) return false;
rx.lastIndex = index;
return rx.test(text);
}
})();

View File

@ -3,6 +3,7 @@
'use strict';
(window.CodeMirror ? window.CodeMirror.prototype : window).colorpicker = function () {
const {constrain} = colorConverter;
const cm = window.CodeMirror && this;
const CSS_PREFIX = 'colorpicker-';
const HUE_COLORS = [
@ -40,8 +41,6 @@
let /** @type {HTMLElement} */ $palette;
const $inputGroups = {};
const $inputs = {};
const $rgb = {};
const $hsl = {};
const $hexLettercase = {};
const allowInputFocus = !('ontouchstart' in document) || window.innerHeight > 800;
@ -85,6 +84,21 @@
return Object.assign(el, props);
}
const alphaPattern = /^\s*(0+\.?|0*\.\d+|0*1\.?|0*1\.0*)?\s*$/.source;
const nestedObj = (obj, key) => (obj[key] || (obj[key] = {}));
const makeNum = (type, channel, props, min, max) =>
$(['input-field', `${type}-${channel}`], [
(nestedObj($inputs, type)[channel] =
$('input', props || {tag: 'input', type: 'number', min, max, step: 1})),
$('title', channel.toUpperCase()),
]);
const ColorGroup = (type, channels) => (
$inputGroups[type] = $(['input-group', type], [
...Object.entries(channels).map(([k, v]) =>
makeNum(type, k, null, v[0], v[1])),
makeNum(type, 'a',
{tag: 'input', type: 'text', pattern: alphaPattern, spellcheck: false}),
])
);
$root = $('popup', {
oninput: setFromInputs,
onkeydown: setFromKeyboard,
@ -128,42 +142,9 @@
]),
]),
]),
$inputGroups.rgb = $(['input-group', 'rgb'], [
$(['input-field', 'rgb-r'], [
$rgb.r = $('input', {tag: 'input', type: 'number', min: 0, max: 255, step: 1}),
$('title', 'R'),
]),
$(['input-field', 'rgb-g'], [
$rgb.g = $('input', {tag: 'input', type: 'number', min: 0, max: 255, step: 1}),
$('title', 'G'),
]),
$(['input-field', 'rgb-b'], [
$rgb.b = $('input', {tag: 'input', type: 'number', min: 0, max: 255, step: 1}),
$('title', 'B'),
]),
$(['input-field', 'rgb-a'], [
$rgb.a = $('input', {tag: 'input', type: 'text', pattern: alphaPattern, spellcheck: false}),
$('title', 'A'),
]),
]),
$inputGroups.hsl = $(['input-group', 'hsl'], [
$(['input-field', 'hsl-h'], [
$hsl.h = $('input', {tag: 'input', type: 'number', step: 1}),
$('title', 'H'),
]),
$(['input-field', 'hsl-s'], [
$hsl.s = $('input', {tag: 'input', type: 'number', min: 0, max: 100, step: 1}),
$('title', 'S'),
]),
$(['input-field', 'hsl-l'], [
$hsl.l = $('input', {tag: 'input', type: 'number', min: 0, max: 100, step: 1}),
$('title', 'L'),
]),
$(['input-field', 'hsl-a'], [
$hsl.a = $('input', {tag: 'input', type: 'text', pattern: alphaPattern, spellcheck: false}),
$('title', 'A'),
]),
]),
ColorGroup('rgb', {r: [0, 255], g: [0, 255], b: [0, 255]}),
ColorGroup('hsl', {h: [], s: [0, 100], l: [0, 100]}),
ColorGroup('hwb', {h: [], w: [0, 100], b: [0, 100]}),
$('format-change', [
$formatChangeButton = $('format-change-button', {onclick: setFromFormatElement}, '↔'),
]),
@ -180,19 +161,26 @@
}),
]);
$inputs.hex = [$hexCode];
$inputs.rgb = [$rgb.r, $rgb.g, $rgb.b, $rgb.a];
$inputs.hsl = [$hsl.h, $hsl.s, $hsl.l, $hsl.a];
const inputsToArray = inputs => inputs.map(el => parseFloat(el.value));
const inputsToHexString = () => $hexCode.value.trim();
const inputsToRGB = ([r, g, b, a] = inputsToArray($inputs.rgb)) => ({r, g, b, a, type: 'rgb'});
const inputsToHSL = ([h, s, l, a] = inputsToArray($inputs.hsl)) => ({h, s, l, a, type: 'hsl'});
Object.defineProperty($inputs.hex, 'color', {get: inputsToHexString});
Object.defineProperty($inputs.rgb, 'color', {get: inputsToRGB});
Object.defineProperty($inputs.hsl, 'color', {get: inputsToHSL});
Object.defineProperty($inputs, 'color', {get: () => $inputs[currentFormat].color});
const inputsToObj = type => {
const res = {type};
for (const [k, el] of Object.entries($inputs[type])) {
res[k] = parseFloat(el.value);
}
return res;
};
for (const [key, val] of Object.entries($inputs)) {
Object.defineProperty(val, 'color', {
get: inputsToObj.bind(null, key),
});
}
Object.defineProperty($inputs.hex = [$hexCode], 'color', {
get: () => $hexCode.value.trim(),
});
Object.defineProperty($inputs, 'color', {
get: () => $inputs[currentFormat].color,
});
Object.defineProperty($inputs, 'colorString', {
get: () => currentFormat && colorConverter.format($inputs[currentFormat].color),
get: () => currentFormat && colorConverter.format($inputs[currentFormat].color, undefined, {round: true}),
});
HUE_COLORS.forEach(color => Object.assign(color, colorConverter.parse(color.hex)));
@ -210,6 +198,7 @@
HSV = {};
currentFormat = '';
options = PUBLIC_API.options = opt;
if (opt.round !== false) opt.round = true;
prevFocusedElement = document.activeElement;
userActivity = 0;
lastOutputColor = opt.color || '';
@ -246,33 +235,19 @@
}
function setColor(color) {
switch (typeof color) {
case 'string':
if (typeof color === 'string') {
color = colorConverter.parse(color);
break;
case 'object': {
const {r, g, b, a} = color;
if (!isNaN(r) && !isNaN(g) && !isNaN(b)) {
color = {r, g, b, a, type: 'rgb'};
break;
} else if (typeof color === 'object' && color && !color.type) {
color = Object.assign({}, color, {type: colorConverter.guessType(color)});
}
const {h, s, l} = color;
if (!isNaN(h) && !isNaN(s) && !isNaN(l)) {
color = {h, s, l, a, type: 'hsl'};
break;
}
}
// fallthrough
default:
if (!color || !color.type) {
return false;
}
if (color) {
if (!initialized) {
init();
}
setFromColor(color);
}
return Boolean(color);
return true;
}
function getColor(type) {
@ -280,9 +255,7 @@
return;
}
readCurrentColorFromRamps();
const color = type === 'hsl' ?
colorConverter.HSVtoHSL(HSV) :
colorConverter.HSVtoRGB(HSV);
const color = colorConverter.fromHSV(HSV, type);
return type ? colorToString(color, type) : color;
}
@ -341,7 +314,7 @@
function setFromFormatElement({shiftKey}) {
userActivity = performance.now();
HSV.a = isNaN(HSV.a) ? 1 : HSV.a;
const formats = ['hex', 'rgb', 'hsl'];
const formats = Object.keys($inputGroups);
const dir = shiftKey ? -1 : 1;
const total = formats.length;
if ($inputs.colorString === $inputs.prevColorString) {
@ -362,7 +335,7 @@
function setFromInputs(event) {
userActivity = event ? performance.now() : userActivity;
if ($inputs[currentFormat].every(validateInput)) {
if (Object.values($inputs[currentFormat]).every(validateInput)) {
setFromColor($inputs.color);
}
}
@ -375,7 +348,7 @@
case 'PageDown':
if (!ctrl && !alt && !meta) {
const el = document.activeElement;
const inputs = $inputs[currentFormat];
const inputs = Object.values($inputs[currentFormat]);
const lastInput = inputs[inputs.length - 1];
if (key === 'Tab' && shift && el === inputs[0]) {
maybeFocus(lastInput);
@ -424,8 +397,8 @@
newValue = options.hexUppercase ? newValue.toUpperCase() : newValue.toLowerCase();
} else if (!alt) {
value = parseFloat(el.value);
const isHue = el === $inputs.hsl[0];
const isAlpha = el === $inputs[currentFormat][3];
const isHue = el.title === 'H';
const isAlpha = el === $inputs[currentFormat].a;
const isRGB = currentFormat === 'rgb';
const min = isHue ? -360 : 0;
const max = isHue ? 360 : isAlpha ? 1 : isRGB ? 255 : 100;
@ -446,7 +419,7 @@
}
function validateInput(el) {
const isAlpha = el === $inputs[currentFormat][3];
const isAlpha = el === $inputs[currentFormat].a;
let isValid = (isAlpha || el.value.trim()) && el.checkValidity();
if (!isAlpha && !isValid && currentFormat === 'rgb') {
isValid = parseAs(el, parseInt);
@ -464,9 +437,7 @@
function setFromColor(color) {
color = typeof color === 'string' ? colorConverter.parse(color) : color;
color = color || colorConverter.parse('#f00');
const newHSV = color.type === 'hsl' ?
colorConverter.HSLtoHSV(color) :
colorConverter.RGBtoHSV(color);
const newHSV = colorConverter.toHSV(color);
if (Object.entries(newHSV).every(([k, v]) => v === HSV[k] || Math.abs(v - HSV[k]) < 1e-3)) {
return;
}
@ -488,7 +459,7 @@
}
}
$inputGroups[format].dataset.active = '';
maybeFocus($inputs[format][0]);
maybeFocus(Object.values($inputs[format])[0]);
currentFormat = format;
}
@ -510,25 +481,13 @@
}
function renderInputs() {
const rgb = colorConverter.HSVtoRGB(HSV);
switch (currentFormat) {
case 'hex':
rgb.a = HSV.a;
const rgb = colorConverter.fromHSV(HSV, 'rgb');
if (currentFormat === 'hex') {
$hexCode.value = colorToString(rgb, 'hex');
break;
case 'rgb': {
$rgb.r.value = rgb.r;
$rgb.g.value = rgb.g;
$rgb.b.value = rgb.b;
$rgb.a.value = alphaToString() || 1;
break;
}
case 'hsl': {
const {h, s, l} = colorConverter.HSVtoHSL(HSV);
$hsl.h.value = h;
$hsl.s.value = s;
$hsl.l.value = l;
$hsl.a.value = alphaToString() || 1;
} else {
for (const [k, v] of Object.entries(colorConverter.fromHSV(HSV, currentFormat))) {
const el = $inputs[currentFormat][k];
if (el) el.value = k === 'a' ? alphaToString() || 1 : Math.round(v);
}
}
$swatch.style.backgroundColor = colorToString(rgb, 'rgb');
@ -704,7 +663,7 @@
}
if (
userActivity &&
$inputs[currentFormat].every(el => el.checkValidity())
Object.values($inputs[currentFormat]).every(el => el.checkValidity())
) {
lastOutputColor = colorString.replace(/\b0\./g, '.');
if (isCallable) {
@ -770,7 +729,7 @@
//region Color conversion utilities
function colorToString(color, type = currentFormat) {
return colorConverter.format(color, type, options.hexUppercase);
return colorConverter.format(color, type, options);
}
function alphaToString(a = HSV.a) {
@ -778,9 +737,7 @@
}
function currentColorToString(format = currentFormat, alpha = HSV.a) {
const converted = format === 'hsl' ?
colorConverter.HSVtoHSL(HSV) :
colorConverter.HSVtoRGB(HSV);
const converted = colorConverter.fromHSV(HSV, format);
converted.a = isNaN(alpha) || alpha === 1 ? undefined : alpha;
return colorToString(converted, format);
}
@ -879,10 +836,6 @@
return bgLuma < .5 ? 'dark' : 'light';
}
function constrain(min, max, value) {
return value < min ? min : value > max ? max : value;
}
function parseAs(el, parser) {
const num = parser(el.value);
if (!isNaN(num) &&

View File

@ -8,50 +8,27 @@
const COLORVIEW_CLASS = 'colorview';
const COLORVIEW_SWATCH_CLASS = COLORVIEW_CLASS + '-swatch';
const COLORVIEW_SWATCH_CSS = `--${COLORVIEW_SWATCH_CLASS}:`;
const CLOSE_POPUP_EVENT = 'close-colorpicker-popup';
const RXS_NUM = /\s*([+-]?(?:\d+\.?\d*|\d*\.\d+))(?:e[+-]?\d+)?/.source;
const RX_COLOR = {
hex: /#(?:[a-f\d]{3}(?:[a-f\d](?:[a-f\d]{2}){0,2})?)\b/iy,
rgb: new RegExp([
// num, num, num [ , num_or_pct]?
// pct, pct, pct [ , num_or_pct]?
`^((${RXS_NUM}\\s*(,|$)){3}|(${RXS_NUM}%\\s*(,|$)){3})(${RXS_NUM}%?)?\\s*$`,
// num num num [ / num_or_pct]?
// pct pct pct [ / num_or_pct]?
`^((${RXS_NUM}\\s*(\\s|$)){3}|(${RXS_NUM}%\\s*(\\s|$)){3})(/${RXS_NUM}%?)?\\s*$`,
].join('|'), 'iy'),
hsl: new RegExp([
// num_or_angle, pct, pct [ , num_or_pct]?
`^(${RXS_NUM}(|deg|g?rad|turn)\\s*),(${RXS_NUM}%\\s*(,|$)){2}(${RXS_NUM}%?)?\\s*$`,
// num_or_angle pct pct [ / num_or_pct]?
`^(${RXS_NUM}(|deg|g?rad|turn)\\s*)\\s(${RXS_NUM}%\\s*(\\s|$)){2}(/${RXS_NUM}%?)?\\s*$`,
].join('|'), 'iy'),
unsupported: new RegExp([
const {RX_COLOR, testAt} = colorConverter;
const RX_UNSUPPORTED = (s => s && new RegExp(s))([
!CSS.supports('color', '#abcd') && /#(.{4}){1,2}$/,
!CSS.supports('color', 'hwb(1 0% 0%)') && /^hwb\(/,
!CSS.supports('color', 'rgb(1e2,0,0)') && /\de/,
!CSS.supports('color', 'rgb(1.5,0,0)') && /^rgba?\((([^,]+,){0,2}[^,]*\.|(\s*\S+\s+){0,2}\S*\.)/,
!CSS.supports('color', 'rgb(1.5,0,0)') &&
/^rgba?\((([^,]+,){0,2}[^,]*\.|(\s*\S+\s+){0,2}\S*\.)/,
!CSS.supports('color', 'rgb(1,2,3,.5)') && /[^a]\(([^,]+,){3}/,
!CSS.supports('color', 'rgb(1,2,3,50%)') && /\((([^,]+,){3}|(\s*\S+[\s/]+){3}).*?%/,
!CSS.supports('color', 'rgb(1 2 3 / 1)') && /^[^,]+$/,
!CSS.supports('color', 'hsl(1turn, 2%, 3%)') && /deg|g?rad|turn/,
].filter(Boolean).map(rx => rx.source).join('|') || '^$', 'i'),
};
if (RX_COLOR.unsupported.source === '^$') {
RX_COLOR.unsupported = null;
}
].filter(Boolean).map(rx => rx.source).join('|'));
const RX_DETECT = new RegExp('(^|[\\s(){}[\\]:,/"=])' +
'(' +
RX_COLOR.hex.source + '|' +
'(?:rgb|hsl)a?(?=\\()|(?:' + [...colorConverter.NAMED_COLORS.keys()].join('|') + ')' +
'(?:(?:rgb|hsl)a?|hwb)(?=\\()|(?:' + [...colorConverter.NAMED_COLORS.keys()].join('|') + ')' +
'(?=[\\s;(){}[\\]/"!]|$)' +
')', 'gi');
const RX_DETECT_FUNC = /(rgb|hsl)a?\(/iy;
const RX_DETECT_FUNC = /((rgb|hsl)a?|hwb)\(/iy;
const RX_COMMENT = /\/\*([^*]|\*(?!\/))*(\*\/|$)/g;
const SPACE1K = ' '.repeat(1000);
@ -439,7 +416,7 @@
function getSafeColorValue() {
if (isHex && color.length !== 5 && color.length !== 9) return color;
if (!RX_COLOR.unsupported || !RX_COLOR.unsupported.test(color)) return color;
if (!RX_UNSUPPORTED || !RX_UNSUPPORTED.test(color)) return color;
const value = colorConverter.parse(color);
return colorConverter.format(value, 'rgb');
}
@ -710,14 +687,6 @@
setTimeout(() => el.remove(), DURATION_SEC * 1000);
}
function testAt(rx, index, text) {
if (!rx) return false;
rx.lastIndex = index;
return rx.test(text);
}
function getStyleAtPos({
line,
styles = this.getLineHandle(line).styles,

View File

@ -83,7 +83,7 @@ const BUILDERS = Object.assign(Object.create(null), {
if (alpha) delete value.a;
const isRgb = isUsoRgb || value.type === 'rgb' || value.a != null && value.a !== 1;
const usoMode = isUsoRgb || !isRgb;
value = colorConverter.format(value, isRgb ? 'rgb' : 'hex', undefined, usoMode);
value = colorConverter.format(value, isRgb ? 'rgb' : 'hex', {usoMode});
}
return value;
case 'dropdown':