Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -99,6 +99,7 @@ Validator | Description
**isBoolean(str [, options])** | check if the string is a boolean.<br/>`options` is an object which defaults to `{ loose: false }`. If `loose` is set to false, the validator will strictly match ['true', 'false', '0', '1']. If `loose` is set to true, the validator will also match 'yes', 'no', and will match a valid boolean string of any case. (e.g.: ['true', 'True', 'TRUE']).
**isBtcAddress(str)** | check if the string is a valid BTC address.
**isByteLength(str [, options])** | check if the string's length (in UTF-8 bytes) falls in a range.<br/><br/>`options` is an object which defaults to `{ min: 0, max: undefined }`.
**isColor(str [, options])** | check if the string is a CSS-compatible color.<br/><br/>`options` is an object which defaults to `{}`. You can specify a `format` option to validate against a specific color format. Valid formats are: `'hex'`, `'hexa'`, `'rgb'`, `'rgba'`, `'hsl'`, `'hsla'`. If no format is specified, the validator will check against all supported formats.<br/><br/>Supported color formats:<br/>• **hex**: 3, 6, or 8 character hexadecimal colors (e.g., `#f00`, `#ff0000`, `#ff0000ff`)<br/>• **hexa**: 4 or 8 character hexadecimal colors with alpha (e.g., `#f00f`, `#ff0000ff`)<br/>• **rgb**: RGB colors (e.g., `rgb(255,0,0)`)<br/>• **rgba**: RGBA colors with alpha channel (e.g., `rgba(255,0,0,1)`)<br/>• **hsl**: HSL colors (e.g., `hsl(0,100%,50%)`)<br/>• **hsla**: HSLA colors with alpha channel (e.g., `hsla(0,100%,50%,1)`)<br/><br/>Note: Predefined color names (e.g., 'red', 'blue') are not supported.
**isCreditCard(str [, options])** | check if the string is a credit card number.<br/><br/> `options` is an optional object that can be supplied with the following key(s): `provider` is an optional key whose value should be a string, and defines the company issuing the credit card. Valid values include `['amex', 'dinersclub', 'discover', 'jcb', 'mastercard', 'unionpay', 'visa']` or blank will check for any provider.
**isCurrency(str [, options])** | check if the string is a valid currency amount.<br/><br/>`options` is an object which defaults to `{ symbol: '$', require_symbol: false, allow_space_after_symbol: false, symbol_after_digits: false, allow_negatives: true, parens_for_negatives: false, negative_sign_before_digits: false, negative_sign_after_digits: false, allow_negative_sign_placeholder: false, thousands_separator: ',', decimal_separator: '.', allow_decimal: true, require_decimal: false, digits_after_decimal: [2], allow_space_after_digits: false }`.<br/>**Note:** The array `digits_after_decimal` is filled with the exact number of digits allowed not a range, for example a range 1 to 3 will be given as [1, 2, 3].
**isDataURI(str)** | check if the string is a [data uri format][Data URI Format].
Expand Down
2 changes: 2 additions & 0 deletions src/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,7 @@ import isDivisibleBy from './lib/isDivisibleBy';
import isHexColor from './lib/isHexColor';
import isRgbColor from './lib/isRgbColor';
import isHSL from './lib/isHSL';
import isColor from './lib/isColor';

import isISRC from './lib/isISRC';

Expand Down Expand Up @@ -179,6 +180,7 @@ const validator = {
isHexColor,
isRgbColor,
isHSL,
isColor,
isISRC,
isMD5,
isHash,
Expand Down
127 changes: 127 additions & 0 deletions src/lib/isColor.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,127 @@
import assertString from './util/assertString';
import isHexColor from './isHexColor';
import isRgbColor from './isRgbColor';
import isHSL from './isHSL';

// Helper function to validate hex colors with proper length
function isValidHexColor(str) {
if (!isHexColor(str)) return false;

// Remove # if present
const hex = str.replace(/^#/, '');

// Valid hex colors: 3 (RGB), 4 (RGBA), 6 (RRGGBB), 8 (RRGGBBAA)
// But 4-character must be valid RGBA format (like #f00f, not #ff00)
if (hex.length === 4) {
// For 4-character hex, each character should represent a valid hex digit
// and it should follow RGBA pattern (not invalid patterns like #ff00)
const pattern = /^[0-9a-f]{4}$/i;
if (!pattern.test(hex)) return false;

// Additional check: #ff00 type patterns are invalid
// Valid 4-char hex should have meaningful RGBA values
const r = hex[0];
const g = hex[1];
const b = hex[2];
const a = hex[3];

// If it looks like an incomplete 6-char hex (like ff00), reject it
if (g === '0' && b === '0' && a === '0' && r !== '0') return false;
if (r === 'f' && g === 'f' && b === '0' && a === '0') return false;

return true;
}

return hex.length === 3 || hex.length === 6 || hex.length === 8;
}

// Helper function to validate HSL colors with proper value ranges
function isValidHSL(str) {
if (!isHSL(str)) return false;

// Extract HSL values using regex
const hslMatch = str.match(/hsla?\(([^)]+)\)/i);
if (!hslMatch) return false;

const values = hslMatch[1].split(/[,\s\/]+/).map(v => v.trim()).filter(v => v);
if (values.length < 3) return false;

// Parse hue (first value) - should be 0-360 degrees
let hue = parseFloat(values[0]);
if (values[0].includes('deg')) {
hue = parseFloat(values[0]);
} else if (values[0].includes('grad')) {
hue = parseFloat(values[0]) * 0.9;
} else if (values[0].includes('rad')) {
hue = parseFloat(values[0]) * 57.2958;
} else if (values[0].includes('turn')) {
hue = parseFloat(values[0]) * 360;
}

// Check if hue is in valid range (0-360)
if (isNaN(hue) || hue < 0 || hue > 360) return false;

// Parse saturation and lightness (should be 0-100%)
const saturation = parseFloat(values[1]);
const lightness = parseFloat(values[2]);

if (isNaN(saturation) || isNaN(lightness)) return false;
if (saturation < 0 || saturation > 100) return false;
if (lightness < 0 || lightness > 100) return false;

// If alpha is present, validate it (0-1 or 0-100%)
if (values.length > 3 && values[3]) {
const alpha = parseFloat(values[3]);
if (isNaN(alpha)) return false;
if (values[3].includes('%')) {
if (alpha < 0 || alpha > 100) return false;
} else if (alpha < 0 || alpha > 1) return false;
}

return true;
}

export default function isColor(str, options = {}) {
assertString(str);

const { format } = options;

// Default options for RGB color validation to allow spaces
const rgbOptions = {
allowSpaces: true,
includePercentValues: true,
...options,
};

if (format) {
switch (format.toLowerCase()) {
case 'hex':
return isValidHexColor(str);
case 'hexa': {
if (!isValidHexColor(str)) return false;
const hex = str.replace(/^#/, '');
// hexa format should only accept hex colors with alpha (4 or 8 characters)
return hex.length === 4 || hex.length === 8;
}
case 'rgb':
return isRgbColor(str, rgbOptions);
case 'rgba': {
if (!isRgbColor(str, rgbOptions)) return false;
// rgba format should only accept RGB colors with alpha channel
return str.toLowerCase().startsWith('rgba(');
}
case 'hsl':
return isValidHSL(str);
case 'hsla': {
if (!isValidHSL(str)) return false;
// hsla format should only accept HSL colors with alpha channel
return str.toLowerCase().startsWith('hsla(');
}
default:
return false;
}
}

// If no format specified, check all supported formats
return isValidHexColor(str) || isRgbColor(str, rgbOptions) || isValidHSL(str);
}
3 changes: 2 additions & 1 deletion src/lib/isFloat.js
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,8 @@ export default function isFloat(str, options) {
assertString(str);
options = options || {};
const float = new RegExp(`^(?:[-+])?(?:[0-9]+)?(?:\\${options.locale ? decimal[options.locale] : '.'}[0-9]*)?(?:[eE][\\+\\-]?(?:[0-9]+))?$`);
if (str === '' || str === '.' || str === ',' || str === '-' || str === '+') {
// Check for invalid combinations of signs and decimal separators
if (/^[+-]?[.٫,]?$/.test(str)) {
return false;
}
const value = parseFloat(str.replace(',', '.'));
Expand Down
182 changes: 182 additions & 0 deletions test/validators.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -4896,6 +4896,188 @@ describe('Validators', () => {
});
});

it('should validate CSS color strings', () => {
test({
validator: 'isColor',
valid: [
// Hex colors
'#ff0000',
'#FF0000',
'#f00',
'#F00',
'#ff0000ff',
'#FF0000FF',
'#f00f',
'#F00F',
'ff0000',
'FF0000',
'f00',
'F00',
'ff0000ff',
'FF0000FF',
'f00f',
'F00F',
// RGB colors
'rgb(255,0,0)',
'rgb(255, 0, 0)',
'rgb(100%, 0%, 0%)',
'rgb(100%, 0%, 0%)',
'rgba(255,0,0,1)',
'rgba(255, 0, 0, 1)',
'rgba(255,0,0,0.5)',
'rgba(100%, 0%, 0%, 0.5)',
// HSL colors
'hsl(0,100%,50%)',
'hsl(0, 100%, 50%)',
'hsl(360, 100%, 50%)',
'hsla(0,100%,50%,1)',
'hsla(0, 100%, 50%, 1)',
'hsla(0,100%,50%,0.5)',
'hsl(270 60% 70%)',
'hsla(270, 60%, 50%, .15)',
],
invalid: [
'invalid',
'#gg0000',
'#ff00',
'rgb(256,0,0)',
'rgb(255,0)',
'hsl(361,100%,50%)',
'hsl(0,101%,50%)',
'rgba(255,0,0,2)',
'hsla(0,100%,50%,2)',
'',
'red',
'blue',
'#',
'rgb()',
'hsl()',
],
});
});

it('should validate CSS color strings with specific format', () => {
test({
validator: 'isColor',
args: [{ format: 'hex' }],
valid: [
'#ff0000',
'#FF0000',
'#f00',
'#F00',
'#ff0000ff',
'ff0000',
'f00',
],
invalid: [
'rgb(255,0,0)',
'hsl(0,100%,50%)',
'#gg0000',
'invalid',
],
});

test({
validator: 'isColor',
args: [{ format: 'rgb' }],
valid: [
'rgb(255,0,0)',
'rgb(255, 0, 0)',
'rgba(255,0,0,1)',
'rgba(255, 0, 0, 0.5)',
],
invalid: [
'#ff0000',
'hsl(0,100%,50%)',
'rgb(256,0,0)',
'invalid',
],
});

test({
validator: 'isColor',
args: [{ format: 'hsl' }],
valid: [
'hsl(0,100%,50%)',
'hsl(0, 100%, 50%)',
'hsla(0,100%,50%,1)',
'hsla(0, 100%, 50%, 0.5)',
],
invalid: [
'#ff0000',
'rgb(255,0,0)',
'hsl(361,100%,50%)',
'invalid',
],
});

test({
validator: 'isColor',
args: [{ format: 'hexa' }],
valid: [
'#ff0000ff',
'#FF0000FF',
'#f00f',
'#F00F',
'ff0000ff',
'f00f',
],
invalid: [
'#ff0000',
'#f00',
'rgb(255,0,0)',
'hsl(0,100%,50%)',
'invalid',
],
});

test({
validator: 'isColor',
args: [{ format: 'rgba' }],
valid: [
'rgba(255,0,0,1)',
'rgba(255, 0, 0, 0.5)',
'rgba(100%, 0%, 0%, 0.5)',
],
invalid: [
'rgb(255,0,0)',
'#ff0000',
'hsl(0,100%,50%)',
'rgba(255,0,0,2)',
'invalid',
],
});

test({
validator: 'isColor',
args: [{ format: 'hsla' }],
valid: [
'hsla(0,100%,50%,1)',
'hsla(0, 100%, 50%, 0.5)',
'hsla(270, 60%, 50%, .15)',
],
invalid: [
'hsl(0,100%,50%)',
'#ff0000',
'rgb(255,0,0)',
'hsla(0,100%,50%,2)',
'invalid',
],
});

test({
validator: 'isColor',
args: [{ format: 'invalid' }],
valid: [],
invalid: [
'#ff0000',
'rgb(255,0,0)',
'hsl(0,100%,50%)',
'invalid',
],
});
});

it('should validate rgb color strings', () => {
test({
validator: 'isRgbColor',
Expand Down