colorpicker: add hwb colors
This commit is contained in:
parent
f54d145bf5
commit
537372dffa
|
@ -2,29 +2,82 @@
|
||||||
|
|
||||||
const colorConverter = (() => {
|
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 {
|
return {
|
||||||
parse,
|
parse,
|
||||||
format,
|
format,
|
||||||
formatAlpha,
|
formatAlpha,
|
||||||
RGBtoHSV,
|
fromHSV: (color, type) => FROM_HSV[type](color),
|
||||||
HSVtoRGB,
|
toHSV: color => TO_HSV[color.type || 'rgb'](color),
|
||||||
HSLtoHSV,
|
constrain,
|
||||||
HSVtoHSL,
|
|
||||||
constrainHue,
|
constrainHue,
|
||||||
|
guessType,
|
||||||
snapToInt,
|
snapToInt,
|
||||||
|
testAt,
|
||||||
ALPHA_DIGITS: 3,
|
ALPHA_DIGITS: 3,
|
||||||
|
RX_COLOR,
|
||||||
// NAMED_COLORS is added below
|
// 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 : '';
|
if (!color || !type) return typeof color === 'string' ? color : '';
|
||||||
const {a} = color;
|
const {a, type: src = guessType(color)} = color;
|
||||||
let aStr = formatAlpha(a);
|
const aFmt = formatAlpha(a);
|
||||||
if (aStr) aStr = ', ' + aStr;
|
const aStr = aFmt ? ', ' + aFmt : '';
|
||||||
if (type !== 'hsl' && color.type === 'hsl') {
|
const srcConv = src === 'hex' ? 'rgb' : src;
|
||||||
color = HSVtoRGB(HSLtoHSV(color));
|
const dstConv = type === 'hex' ? 'rgb' : type;
|
||||||
}
|
color = srcConv === dstConv ? color : FROM_HSV[dstConv](TO_HSV[srcConv](color));
|
||||||
const {r, g, b, h, s, l} = color;
|
round = round ? Math.round : v => v;
|
||||||
|
const {r, g, b, h, s, l, w} = color;
|
||||||
switch (type) {
|
switch (type) {
|
||||||
case 'hex': {
|
case 'hex': {
|
||||||
let res = '#' + hex2(r) + hex2(g) + hex2(b) + (aStr ? hex2(Math.round(a * 255)) : '');
|
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})`;
|
return usoMode ? rgb : `rgb${aStr ? 'a' : ''}(${rgb}${aStr})`;
|
||||||
}
|
}
|
||||||
case 'hsl':
|
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) {
|
function parse(str) {
|
||||||
if (typeof str !== 'string') return;
|
if (typeof str !== 'string') return;
|
||||||
str = str.trim();
|
str = str.trim().toLowerCase();
|
||||||
if (!str) return;
|
if (!str) return;
|
||||||
|
|
||||||
if (str[0] !== '#' && !str.includes('(')) {
|
if (str[0] !== '#' && !str.includes('(')) {
|
||||||
|
@ -90,47 +107,45 @@ const colorConverter = (() => {
|
||||||
}
|
}
|
||||||
|
|
||||||
if (str[0] === '#') {
|
if (str[0] === '#') {
|
||||||
if (!validateHex(str)) {
|
if (!testAt(RX_COLOR.hex, 0, str)) {
|
||||||
return null;
|
return;
|
||||||
}
|
}
|
||||||
str = str.slice(1);
|
str = str.slice(1);
|
||||||
const [r, g, b, a = 255] = str.length <= 4 ?
|
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 + c, 16)) :
|
||||||
str.match(/(..)/g).map(c => parseInt(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);
|
const [, func, type = func, value] = str.match(/^((rgb|hsl)a?|hwb)\(\s*(.*?)\s*\)|$/);
|
||||||
if (!type) return;
|
if (!func || !testAt(RX_COLOR[type], 0, value)) {
|
||||||
|
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 k = first.endsWith('%') ? 2.55 : 1;
|
const [s1, s2, s3, sA] = value.split(/\s*[,/]\s*|\s+/);
|
||||||
const [r, g, b] = num.map(s => Math.round(parseFloat(s) * k));
|
const a = isNaN(sA) ? 1 : constrain(0, 1, sA / (sA.endsWith('%') ? 100 : 1));
|
||||||
return {type: 'rgb', r, g, b, a};
|
|
||||||
} else {
|
if (type === 'rgb') {
|
||||||
if (!validateHSL(num)) {
|
const k = s1.endsWith('%') ? 2.55 : 1;
|
||||||
return null;
|
return {
|
||||||
}
|
type,
|
||||||
let h = parseFloat(first);
|
r: constrain(0, 255, Math.round(s1 * k)),
|
||||||
if (first.endsWith('grad')) h *= 360 / 400;
|
g: constrain(0, 255, Math.round(s2 * k)),
|
||||||
else if (first.endsWith('rad')) h *= 180 / Math.PI;
|
b: constrain(0, 255, Math.round(s3 * k)),
|
||||||
else if (first.endsWith('turn')) h *= 360;
|
a,
|
||||||
const s = parseFloat(num[1]);
|
};
|
||||||
const l = parseFloat(num[2]);
|
|
||||||
return {type: 'hsl', h, s, l, 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) {
|
function formatAlpha(a) {
|
||||||
|
@ -164,8 +179,8 @@ const colorConverter = (() => {
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
function HSVtoRGB({h, s, v}) {
|
function HSVtoRGB({h, s, v, a}) {
|
||||||
h = constrainHue(h) % 360;
|
h = constrainHue(h);
|
||||||
const C = s * v;
|
const C = s * v;
|
||||||
const X = C * (1 - Math.abs((h / 60) % 2 - 1));
|
const X = C * (1 - Math.abs((h / 60) % 2 - 1));
|
||||||
const m = v - C;
|
const m = v - C;
|
||||||
|
@ -180,9 +195,11 @@ const colorConverter = (() => {
|
||||||
r: snapToInt(Math.round((r + m) * 255)),
|
r: snapToInt(Math.round((r + m) * 255)),
|
||||||
g: snapToInt(Math.round((g + m) * 255)),
|
g: snapToInt(Math.round((g + m) * 255)),
|
||||||
b: snapToInt(Math.round((b + m) * 255)),
|
b: snapToInt(Math.round((b + m) * 255)),
|
||||||
|
a,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
function HSLtoHSV({h, s, l, a}) {
|
function HSLtoHSV({h, s, l, a}) {
|
||||||
const t = s * (l < 50 ? l : 100 - l) / 100;
|
const t = s * (l < 50 ? l : 100 - l) / 100;
|
||||||
return {
|
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 l = (2 - s) * v / 2;
|
||||||
const t = l < .5 ? l * 2 : 2 - l * 2;
|
const t = l < .5 ? l * 2 : 2 - l * 2;
|
||||||
return {
|
return {
|
||||||
h: Math.round(constrainHue(h)),
|
h: constrainHue(h),
|
||||||
s: Math.round(t ? s * v / t * 100 : 0),
|
s: t ? s * v / t * 100 : 0,
|
||||||
l: Math.round(l * 100),
|
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) {
|
function constrainHue(h) {
|
||||||
return h < 0 ? h % 360 + 360 :
|
return h < 0 ? h % 360 + 360 :
|
||||||
h > 360 ? h % 360 :
|
h > 360 ? h % 360 :
|
||||||
|
@ -215,7 +257,13 @@ const colorConverter = (() => {
|
||||||
}
|
}
|
||||||
|
|
||||||
function hex2(val) {
|
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';
|
'use strict';
|
||||||
|
|
||||||
(window.CodeMirror ? window.CodeMirror.prototype : window).colorpicker = function () {
|
(window.CodeMirror ? window.CodeMirror.prototype : window).colorpicker = function () {
|
||||||
|
const {constrain} = colorConverter;
|
||||||
const cm = window.CodeMirror && this;
|
const cm = window.CodeMirror && this;
|
||||||
const CSS_PREFIX = 'colorpicker-';
|
const CSS_PREFIX = 'colorpicker-';
|
||||||
const HUE_COLORS = [
|
const HUE_COLORS = [
|
||||||
|
@ -40,8 +41,6 @@
|
||||||
let /** @type {HTMLElement} */ $palette;
|
let /** @type {HTMLElement} */ $palette;
|
||||||
const $inputGroups = {};
|
const $inputGroups = {};
|
||||||
const $inputs = {};
|
const $inputs = {};
|
||||||
const $rgb = {};
|
|
||||||
const $hsl = {};
|
|
||||||
const $hexLettercase = {};
|
const $hexLettercase = {};
|
||||||
|
|
||||||
const allowInputFocus = !('ontouchstart' in document) || window.innerHeight > 800;
|
const allowInputFocus = !('ontouchstart' in document) || window.innerHeight > 800;
|
||||||
|
@ -85,6 +84,21 @@
|
||||||
return Object.assign(el, props);
|
return Object.assign(el, props);
|
||||||
}
|
}
|
||||||
const alphaPattern = /^\s*(0+\.?|0*\.\d+|0*1\.?|0*1\.0*)?\s*$/.source;
|
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', {
|
$root = $('popup', {
|
||||||
oninput: setFromInputs,
|
oninput: setFromInputs,
|
||||||
onkeydown: setFromKeyboard,
|
onkeydown: setFromKeyboard,
|
||||||
|
@ -128,42 +142,9 @@
|
||||||
]),
|
]),
|
||||||
]),
|
]),
|
||||||
]),
|
]),
|
||||||
$inputGroups.rgb = $(['input-group', 'rgb'], [
|
ColorGroup('rgb', {r: [0, 255], g: [0, 255], b: [0, 255]}),
|
||||||
$(['input-field', 'rgb-r'], [
|
ColorGroup('hsl', {h: [], s: [0, 100], l: [0, 100]}),
|
||||||
$rgb.r = $('input', {tag: 'input', type: 'number', min: 0, max: 255, step: 1}),
|
ColorGroup('hwb', {h: [], w: [0, 100], b: [0, 100]}),
|
||||||
$('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'),
|
|
||||||
]),
|
|
||||||
]),
|
|
||||||
$('format-change', [
|
$('format-change', [
|
||||||
$formatChangeButton = $('format-change-button', {onclick: setFromFormatElement}, '↔'),
|
$formatChangeButton = $('format-change-button', {onclick: setFromFormatElement}, '↔'),
|
||||||
]),
|
]),
|
||||||
|
@ -180,19 +161,26 @@
|
||||||
}),
|
}),
|
||||||
]);
|
]);
|
||||||
|
|
||||||
$inputs.hex = [$hexCode];
|
const inputsToObj = type => {
|
||||||
$inputs.rgb = [$rgb.r, $rgb.g, $rgb.b, $rgb.a];
|
const res = {type};
|
||||||
$inputs.hsl = [$hsl.h, $hsl.s, $hsl.l, $hsl.a];
|
for (const [k, el] of Object.entries($inputs[type])) {
|
||||||
const inputsToArray = inputs => inputs.map(el => parseFloat(el.value));
|
res[k] = parseFloat(el.value);
|
||||||
const inputsToHexString = () => $hexCode.value.trim();
|
}
|
||||||
const inputsToRGB = ([r, g, b, a] = inputsToArray($inputs.rgb)) => ({r, g, b, a, type: 'rgb'});
|
return res;
|
||||||
const inputsToHSL = ([h, s, l, a] = inputsToArray($inputs.hsl)) => ({h, s, l, a, type: 'hsl'});
|
};
|
||||||
Object.defineProperty($inputs.hex, 'color', {get: inputsToHexString});
|
for (const [key, val] of Object.entries($inputs)) {
|
||||||
Object.defineProperty($inputs.rgb, 'color', {get: inputsToRGB});
|
Object.defineProperty(val, 'color', {
|
||||||
Object.defineProperty($inputs.hsl, 'color', {get: inputsToHSL});
|
get: inputsToObj.bind(null, key),
|
||||||
Object.defineProperty($inputs, 'color', {get: () => $inputs[currentFormat].color});
|
});
|
||||||
|
}
|
||||||
|
Object.defineProperty($inputs.hex = [$hexCode], 'color', {
|
||||||
|
get: () => $hexCode.value.trim(),
|
||||||
|
});
|
||||||
|
Object.defineProperty($inputs, 'color', {
|
||||||
|
get: () => $inputs[currentFormat].color,
|
||||||
|
});
|
||||||
Object.defineProperty($inputs, 'colorString', {
|
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)));
|
HUE_COLORS.forEach(color => Object.assign(color, colorConverter.parse(color.hex)));
|
||||||
|
@ -210,6 +198,7 @@
|
||||||
HSV = {};
|
HSV = {};
|
||||||
currentFormat = '';
|
currentFormat = '';
|
||||||
options = PUBLIC_API.options = opt;
|
options = PUBLIC_API.options = opt;
|
||||||
|
if (opt.round !== false) opt.round = true;
|
||||||
prevFocusedElement = document.activeElement;
|
prevFocusedElement = document.activeElement;
|
||||||
userActivity = 0;
|
userActivity = 0;
|
||||||
lastOutputColor = opt.color || '';
|
lastOutputColor = opt.color || '';
|
||||||
|
@ -246,33 +235,19 @@
|
||||||
}
|
}
|
||||||
|
|
||||||
function setColor(color) {
|
function setColor(color) {
|
||||||
switch (typeof color) {
|
if (typeof color === 'string') {
|
||||||
case 'string':
|
|
||||||
color = colorConverter.parse(color);
|
color = colorConverter.parse(color);
|
||||||
break;
|
} else if (typeof color === 'object' && color && !color.type) {
|
||||||
case 'object': {
|
color = Object.assign({}, color, {type: colorConverter.guessType(color)});
|
||||||
const {r, g, b, a} = color;
|
|
||||||
if (!isNaN(r) && !isNaN(g) && !isNaN(b)) {
|
|
||||||
color = {r, g, b, a, type: 'rgb'};
|
|
||||||
break;
|
|
||||||
}
|
}
|
||||||
const {h, s, l} = color;
|
if (!color || !color.type) {
|
||||||
if (!isNaN(h) && !isNaN(s) && !isNaN(l)) {
|
|
||||||
color = {h, s, l, a, type: 'hsl'};
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// fallthrough
|
|
||||||
default:
|
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
if (color) {
|
|
||||||
if (!initialized) {
|
if (!initialized) {
|
||||||
init();
|
init();
|
||||||
}
|
}
|
||||||
setFromColor(color);
|
setFromColor(color);
|
||||||
}
|
return true;
|
||||||
return Boolean(color);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function getColor(type) {
|
function getColor(type) {
|
||||||
|
@ -280,9 +255,7 @@
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
readCurrentColorFromRamps();
|
readCurrentColorFromRamps();
|
||||||
const color = type === 'hsl' ?
|
const color = colorConverter.fromHSV(HSV, type);
|
||||||
colorConverter.HSVtoHSL(HSV) :
|
|
||||||
colorConverter.HSVtoRGB(HSV);
|
|
||||||
return type ? colorToString(color, type) : color;
|
return type ? colorToString(color, type) : color;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -341,7 +314,7 @@
|
||||||
function setFromFormatElement({shiftKey}) {
|
function setFromFormatElement({shiftKey}) {
|
||||||
userActivity = performance.now();
|
userActivity = performance.now();
|
||||||
HSV.a = isNaN(HSV.a) ? 1 : HSV.a;
|
HSV.a = isNaN(HSV.a) ? 1 : HSV.a;
|
||||||
const formats = ['hex', 'rgb', 'hsl'];
|
const formats = Object.keys($inputGroups);
|
||||||
const dir = shiftKey ? -1 : 1;
|
const dir = shiftKey ? -1 : 1;
|
||||||
const total = formats.length;
|
const total = formats.length;
|
||||||
if ($inputs.colorString === $inputs.prevColorString) {
|
if ($inputs.colorString === $inputs.prevColorString) {
|
||||||
|
@ -362,7 +335,7 @@
|
||||||
|
|
||||||
function setFromInputs(event) {
|
function setFromInputs(event) {
|
||||||
userActivity = event ? performance.now() : userActivity;
|
userActivity = event ? performance.now() : userActivity;
|
||||||
if ($inputs[currentFormat].every(validateInput)) {
|
if (Object.values($inputs[currentFormat]).every(validateInput)) {
|
||||||
setFromColor($inputs.color);
|
setFromColor($inputs.color);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -375,7 +348,7 @@
|
||||||
case 'PageDown':
|
case 'PageDown':
|
||||||
if (!ctrl && !alt && !meta) {
|
if (!ctrl && !alt && !meta) {
|
||||||
const el = document.activeElement;
|
const el = document.activeElement;
|
||||||
const inputs = $inputs[currentFormat];
|
const inputs = Object.values($inputs[currentFormat]);
|
||||||
const lastInput = inputs[inputs.length - 1];
|
const lastInput = inputs[inputs.length - 1];
|
||||||
if (key === 'Tab' && shift && el === inputs[0]) {
|
if (key === 'Tab' && shift && el === inputs[0]) {
|
||||||
maybeFocus(lastInput);
|
maybeFocus(lastInput);
|
||||||
|
@ -424,8 +397,8 @@
|
||||||
newValue = options.hexUppercase ? newValue.toUpperCase() : newValue.toLowerCase();
|
newValue = options.hexUppercase ? newValue.toUpperCase() : newValue.toLowerCase();
|
||||||
} else if (!alt) {
|
} else if (!alt) {
|
||||||
value = parseFloat(el.value);
|
value = parseFloat(el.value);
|
||||||
const isHue = el === $inputs.hsl[0];
|
const isHue = el.title === 'H';
|
||||||
const isAlpha = el === $inputs[currentFormat][3];
|
const isAlpha = el === $inputs[currentFormat].a;
|
||||||
const isRGB = currentFormat === 'rgb';
|
const isRGB = currentFormat === 'rgb';
|
||||||
const min = isHue ? -360 : 0;
|
const min = isHue ? -360 : 0;
|
||||||
const max = isHue ? 360 : isAlpha ? 1 : isRGB ? 255 : 100;
|
const max = isHue ? 360 : isAlpha ? 1 : isRGB ? 255 : 100;
|
||||||
|
@ -446,7 +419,7 @@
|
||||||
}
|
}
|
||||||
|
|
||||||
function validateInput(el) {
|
function validateInput(el) {
|
||||||
const isAlpha = el === $inputs[currentFormat][3];
|
const isAlpha = el === $inputs[currentFormat].a;
|
||||||
let isValid = (isAlpha || el.value.trim()) && el.checkValidity();
|
let isValid = (isAlpha || el.value.trim()) && el.checkValidity();
|
||||||
if (!isAlpha && !isValid && currentFormat === 'rgb') {
|
if (!isAlpha && !isValid && currentFormat === 'rgb') {
|
||||||
isValid = parseAs(el, parseInt);
|
isValid = parseAs(el, parseInt);
|
||||||
|
@ -464,9 +437,7 @@
|
||||||
function setFromColor(color) {
|
function setFromColor(color) {
|
||||||
color = typeof color === 'string' ? colorConverter.parse(color) : color;
|
color = typeof color === 'string' ? colorConverter.parse(color) : color;
|
||||||
color = color || colorConverter.parse('#f00');
|
color = color || colorConverter.parse('#f00');
|
||||||
const newHSV = color.type === 'hsl' ?
|
const newHSV = colorConverter.toHSV(color);
|
||||||
colorConverter.HSLtoHSV(color) :
|
|
||||||
colorConverter.RGBtoHSV(color);
|
|
||||||
if (Object.entries(newHSV).every(([k, v]) => v === HSV[k] || Math.abs(v - HSV[k]) < 1e-3)) {
|
if (Object.entries(newHSV).every(([k, v]) => v === HSV[k] || Math.abs(v - HSV[k]) < 1e-3)) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
@ -488,7 +459,7 @@
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
$inputGroups[format].dataset.active = '';
|
$inputGroups[format].dataset.active = '';
|
||||||
maybeFocus($inputs[format][0]);
|
maybeFocus(Object.values($inputs[format])[0]);
|
||||||
currentFormat = format;
|
currentFormat = format;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -510,25 +481,13 @@
|
||||||
}
|
}
|
||||||
|
|
||||||
function renderInputs() {
|
function renderInputs() {
|
||||||
const rgb = colorConverter.HSVtoRGB(HSV);
|
const rgb = colorConverter.fromHSV(HSV, 'rgb');
|
||||||
switch (currentFormat) {
|
if (currentFormat === 'hex') {
|
||||||
case 'hex':
|
|
||||||
rgb.a = HSV.a;
|
|
||||||
$hexCode.value = colorToString(rgb, 'hex');
|
$hexCode.value = colorToString(rgb, 'hex');
|
||||||
break;
|
} else {
|
||||||
case 'rgb': {
|
for (const [k, v] of Object.entries(colorConverter.fromHSV(HSV, currentFormat))) {
|
||||||
$rgb.r.value = rgb.r;
|
const el = $inputs[currentFormat][k];
|
||||||
$rgb.g.value = rgb.g;
|
if (el) el.value = k === 'a' ? alphaToString() || 1 : Math.round(v);
|
||||||
$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;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
$swatch.style.backgroundColor = colorToString(rgb, 'rgb');
|
$swatch.style.backgroundColor = colorToString(rgb, 'rgb');
|
||||||
|
@ -704,7 +663,7 @@
|
||||||
}
|
}
|
||||||
if (
|
if (
|
||||||
userActivity &&
|
userActivity &&
|
||||||
$inputs[currentFormat].every(el => el.checkValidity())
|
Object.values($inputs[currentFormat]).every(el => el.checkValidity())
|
||||||
) {
|
) {
|
||||||
lastOutputColor = colorString.replace(/\b0\./g, '.');
|
lastOutputColor = colorString.replace(/\b0\./g, '.');
|
||||||
if (isCallable) {
|
if (isCallable) {
|
||||||
|
@ -770,7 +729,7 @@
|
||||||
//region Color conversion utilities
|
//region Color conversion utilities
|
||||||
|
|
||||||
function colorToString(color, type = currentFormat) {
|
function colorToString(color, type = currentFormat) {
|
||||||
return colorConverter.format(color, type, options.hexUppercase);
|
return colorConverter.format(color, type, options);
|
||||||
}
|
}
|
||||||
|
|
||||||
function alphaToString(a = HSV.a) {
|
function alphaToString(a = HSV.a) {
|
||||||
|
@ -778,9 +737,7 @@
|
||||||
}
|
}
|
||||||
|
|
||||||
function currentColorToString(format = currentFormat, alpha = HSV.a) {
|
function currentColorToString(format = currentFormat, alpha = HSV.a) {
|
||||||
const converted = format === 'hsl' ?
|
const converted = colorConverter.fromHSV(HSV, format);
|
||||||
colorConverter.HSVtoHSL(HSV) :
|
|
||||||
colorConverter.HSVtoRGB(HSV);
|
|
||||||
converted.a = isNaN(alpha) || alpha === 1 ? undefined : alpha;
|
converted.a = isNaN(alpha) || alpha === 1 ? undefined : alpha;
|
||||||
return colorToString(converted, format);
|
return colorToString(converted, format);
|
||||||
}
|
}
|
||||||
|
@ -879,10 +836,6 @@
|
||||||
return bgLuma < .5 ? 'dark' : 'light';
|
return bgLuma < .5 ? 'dark' : 'light';
|
||||||
}
|
}
|
||||||
|
|
||||||
function constrain(min, max, value) {
|
|
||||||
return value < min ? min : value > max ? max : value;
|
|
||||||
}
|
|
||||||
|
|
||||||
function parseAs(el, parser) {
|
function parseAs(el, parser) {
|
||||||
const num = parser(el.value);
|
const num = parser(el.value);
|
||||||
if (!isNaN(num) &&
|
if (!isNaN(num) &&
|
||||||
|
|
|
@ -8,50 +8,27 @@
|
||||||
const COLORVIEW_CLASS = 'colorview';
|
const COLORVIEW_CLASS = 'colorview';
|
||||||
const COLORVIEW_SWATCH_CLASS = COLORVIEW_CLASS + '-swatch';
|
const COLORVIEW_SWATCH_CLASS = COLORVIEW_CLASS + '-swatch';
|
||||||
const COLORVIEW_SWATCH_CSS = `--${COLORVIEW_SWATCH_CLASS}:`;
|
const COLORVIEW_SWATCH_CSS = `--${COLORVIEW_SWATCH_CLASS}:`;
|
||||||
|
|
||||||
const CLOSE_POPUP_EVENT = 'close-colorpicker-popup';
|
const CLOSE_POPUP_EVENT = 'close-colorpicker-popup';
|
||||||
|
|
||||||
const RXS_NUM = /\s*([+-]?(?:\d+\.?\d*|\d*\.\d+))(?:e[+-]?\d+)?/.source;
|
const {RX_COLOR, testAt} = colorConverter;
|
||||||
const RX_COLOR = {
|
const RX_UNSUPPORTED = (s => s && new RegExp(s))([
|
||||||
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([
|
|
||||||
!CSS.supports('color', '#abcd') && /#(.{4}){1,2}$/,
|
!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(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,.5)') && /[^a]\(([^,]+,){3}/,
|
||||||
!CSS.supports('color', 'rgb(1,2,3,50%)') && /\((([^,]+,){3}|(\s*\S+[\s/]+){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', 'rgb(1 2 3 / 1)') && /^[^,]+$/,
|
||||||
!CSS.supports('color', 'hsl(1turn, 2%, 3%)') && /deg|g?rad|turn/,
|
!CSS.supports('color', 'hsl(1turn, 2%, 3%)') && /deg|g?rad|turn/,
|
||||||
].filter(Boolean).map(rx => rx.source).join('|') || '^$', 'i'),
|
].filter(Boolean).map(rx => rx.source).join('|'));
|
||||||
};
|
|
||||||
if (RX_COLOR.unsupported.source === '^$') {
|
|
||||||
RX_COLOR.unsupported = null;
|
|
||||||
}
|
|
||||||
const RX_DETECT = new RegExp('(^|[\\s(){}[\\]:,/"=])' +
|
const RX_DETECT = new RegExp('(^|[\\s(){}[\\]:,/"=])' +
|
||||||
'(' +
|
'(' +
|
||||||
RX_COLOR.hex.source + '|' +
|
RX_COLOR.hex.source + '|' +
|
||||||
'(?:rgb|hsl)a?(?=\\()|(?:' + [...colorConverter.NAMED_COLORS.keys()].join('|') + ')' +
|
'(?:(?:rgb|hsl)a?|hwb)(?=\\()|(?:' + [...colorConverter.NAMED_COLORS.keys()].join('|') + ')' +
|
||||||
'(?=[\\s;(){}[\\]/"!]|$)' +
|
'(?=[\\s;(){}[\\]/"!]|$)' +
|
||||||
')', 'gi');
|
')', 'gi');
|
||||||
const RX_DETECT_FUNC = /(rgb|hsl)a?\(/iy;
|
const RX_DETECT_FUNC = /((rgb|hsl)a?|hwb)\(/iy;
|
||||||
|
|
||||||
const RX_COMMENT = /\/\*([^*]|\*(?!\/))*(\*\/|$)/g;
|
const RX_COMMENT = /\/\*([^*]|\*(?!\/))*(\*\/|$)/g;
|
||||||
const SPACE1K = ' '.repeat(1000);
|
const SPACE1K = ' '.repeat(1000);
|
||||||
|
|
||||||
|
@ -439,7 +416,7 @@
|
||||||
|
|
||||||
function getSafeColorValue() {
|
function getSafeColorValue() {
|
||||||
if (isHex && color.length !== 5 && color.length !== 9) return color;
|
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);
|
const value = colorConverter.parse(color);
|
||||||
return colorConverter.format(value, 'rgb');
|
return colorConverter.format(value, 'rgb');
|
||||||
}
|
}
|
||||||
|
@ -710,14 +687,6 @@
|
||||||
setTimeout(() => el.remove(), DURATION_SEC * 1000);
|
setTimeout(() => el.remove(), DURATION_SEC * 1000);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
function testAt(rx, index, text) {
|
|
||||||
if (!rx) return false;
|
|
||||||
rx.lastIndex = index;
|
|
||||||
return rx.test(text);
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
function getStyleAtPos({
|
function getStyleAtPos({
|
||||||
line,
|
line,
|
||||||
styles = this.getLineHandle(line).styles,
|
styles = this.getLineHandle(line).styles,
|
||||||
|
|
|
@ -83,7 +83,7 @@ const BUILDERS = Object.assign(Object.create(null), {
|
||||||
if (alpha) delete value.a;
|
if (alpha) delete value.a;
|
||||||
const isRgb = isUsoRgb || value.type === 'rgb' || value.a != null && value.a !== 1;
|
const isRgb = isUsoRgb || value.type === 'rgb' || value.a != null && value.a !== 1;
|
||||||
const usoMode = isUsoRgb || !isRgb;
|
const usoMode = isUsoRgb || !isRgb;
|
||||||
value = colorConverter.format(value, isRgb ? 'rgb' : 'hex', undefined, usoMode);
|
value = colorConverter.format(value, isRgb ? 'rgb' : 'hex', {usoMode});
|
||||||
}
|
}
|
||||||
return value;
|
return value;
|
||||||
case 'dropdown':
|
case 'dropdown':
|
||||||
|
|
Loading…
Reference in New Issue
Block a user