diff --git a/index.js b/index.js index cf6b894..2a0615b 100644 --- a/index.js +++ b/index.js @@ -1,8 +1,18 @@ +// TODO: Remove this ignore comment. +// eslint-disable-next-line no-mixed-operators +const toHex = (red, green, blue, alpha) => ((blue | green << 8 | red << 16) | 1 << 24).toString(16).slice(1) + alpha; + export default function rgbHex(red, green, blue, alpha) { - const isPercent = (red + (alpha || '')).toString().includes('%'); + let isPercent = (red + (alpha || '')).toString().includes('%'); + + if (typeof red === 'string' && !green) { // Single string parameter. + const parsed = parseCssRgbString(red); + if (!parsed) { + throw new TypeError('Invalid or unsupported color format.'); + } - if (typeof red === 'string') { - [red, green, blue, alpha] = red.match(/(0?\.?\d+)%?\b/g).map(component => Number(component)); + isPercent = false; + [red, green, blue, alpha] = parsed; } else if (alpha !== undefined) { alpha = Number.parseFloat(alpha); } @@ -31,7 +41,33 @@ export default function rgbHex(red, green, blue, alpha) { alpha = ''; } - // TODO: Remove this ignore comment. - // eslint-disable-next-line no-mixed-operators - return ((blue | green << 8 | red << 16) | 1 << 24).toString(16).slice(1) + alpha; + return toHex(red, green, blue, alpha); } + +const parseCssRgbString = input => { + const parts = input.replace(/rgba?\(([^)]+)\)/, '$1').split(/[,\s/]+/).filter(Boolean); + if (parts.length < 3) { + return; + } + + const parseValue = (value, max) => { + value = value.trim(); + + if (value.endsWith('%')) { + return Math.min(Number.parseFloat(value) * max / 100, max); + } + + return Math.min(Number.parseFloat(value), max); + }; + + const red = parseValue(parts[0], 255); + const green = parseValue(parts[1], 255); + const blue = parseValue(parts[2], 255); + let alpha; + + if (parts.length === 4) { + alpha = parseValue(parts[3], 1); + } + + return [red, green, blue, alpha]; +}; diff --git a/test.js b/test.js index 615e522..324dfb1 100644 --- a/test.js +++ b/test.js @@ -4,16 +4,30 @@ import rgbHex from './index.js'; test('main', t => { t.is(rgbHex(0, 0, 0), '000000'); t.is(rgbHex(65, 131, 196), '4183c4'); - t.is(rgbHex('255 154 253'), 'ff9afd'); - t.is(rgbHex('rgb(40, 42, 54)'), '282a36'); t.is(rgbHex(0, 0, 0, 0), '00000000'); t.is(rgbHex(0, 0, 0, 0.5), '00000080'); t.is(rgbHex(0, 0, 0, '50%'), '00000080'); t.is(rgbHex(0, 0, 0, '100%'), '000000ff'); t.is(rgbHex(65, 131, 196, 0.2), '4183c433'); - t.is(rgbHex('255 154 253, 0.8'), 'ff9afdcc'); - t.is(rgbHex('160 82 45 .4'), 'a0522d66'); t.is(rgbHex(40, 42, 54, 0.75), '282a36bf'); t.is(rgbHex(40, 42, 54, '75%'), '282a36bf'); +}); + +test('string - value', t => { t.is(rgbHex('rgba(40, 42, 54, 75%)'), '282a36bf'); + t.is(rgbHex('rgba( 40, 42, 54, 75% )'), '282a36bf'); + t.is(rgbHex('40, 42, 54, 75%'), '282a36bf'); + t.is(rgbHex('40, 42, 54'), '282a36'); + t.is(rgbHex('255 154 253'), 'ff9afd'); + t.is(rgbHex('255 154 253, 0.8'), 'ff9afdcc'); + t.is(rgbHex('160 82 45 .4'), 'a0522d66'); +}); + +test('string - percentage', t => { + t.is(rgbHex('rgb(30%, 20%, 50%)'), '4c337f'); + t.is(rgbHex('100%, 50%, 0%'), 'ff7f00'); + t.is(rgbHex('30%, 20%, 50%'), '4c337f'); + t.is(rgbHex('30%, 20%, 50%, 50%'), '4c337f80'); + t.is(rgbHex('30% 20% 50%'), '4c337f'); + t.is(rgbHex('30% 20% 50% / 50%'), '4c337f80'); });