colorpicker: add hwb colors
This commit is contained in:
parent
f54d145bf5
commit
537372dffa
|
@ -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);
|
||||
}
|
||||
})();
|
||||
|
||||
|
|
|
@ -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) &&
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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':
|
||||
|
|
Loading…
Reference in New Issue
Block a user