diff --git a/README.md b/README.md
index a0ea7a6..ed9d4c3 100644
--- a/README.md
+++ b/README.md
@@ -99,6 +99,7 @@ Validator | Description
**isBoolean(str [, options])** | check if the string is a boolean.
`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.
`options` is an object which defaults to `{ min: 0, max: undefined }`.
+**isColor(str [, options])** | check if the string is a CSS-compatible color.
`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.
Supported color formats:
• **hex**: 3, 6, or 8 character hexadecimal colors (e.g., `#f00`, `#ff0000`, `#ff0000ff`)
• **hexa**: 4 or 8 character hexadecimal colors with alpha (e.g., `#f00f`, `#ff0000ff`)
• **rgb**: RGB colors (e.g., `rgb(255,0,0)`)
• **rgba**: RGBA colors with alpha channel (e.g., `rgba(255,0,0,1)`)
• **hsl**: HSL colors (e.g., `hsl(0,100%,50%)`)
• **hsla**: HSLA colors with alpha channel (e.g., `hsla(0,100%,50%,1)`)
Note: Predefined color names (e.g., 'red', 'blue') are not supported.
**isCreditCard(str [, options])** | check if the string is a credit card number.
`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.
`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 }`.
**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].
@@ -159,6 +160,7 @@ Validator | Description
**isPort(str)** | check if the string is a valid port number.
**isPostalCode(str, locale)** | check if the string is a postal code.
`locale` is one of `['AD', 'AT', 'AU', 'AZ', 'BA', 'BD', 'BE', 'BG', 'BR', 'BY', 'CA', 'CH', 'CN', 'CO', 'CZ', 'DE', 'DK', 'DO', 'DZ', 'EE', 'ES', 'FI', 'FR', 'GB', 'GR', 'HR', 'HT', 'HU', 'ID', 'IE', 'IL', 'IN', 'IR', 'IS', 'IT', 'JP', 'KE', 'KR', 'LI', 'LK', 'LT', 'LU', 'LV', 'MG', 'MT', 'MX', 'MY', 'NL', 'NO', 'NP', 'NZ', 'PK', 'PL', 'PR', 'PT', 'RO', 'RU', 'SA', 'SE', 'SG', 'SI', 'SK', 'TH', 'TN', 'TW', 'UA', 'US', 'ZA', 'ZM']` OR `'any'`. If 'any' is used, function will check if any of the locales match. Locale list is `validator.isPostalCodeLocales`.
**isRFC3339(str)** | check if the string is a valid [RFC 3339][RFC 3339] date.
+**isRG(str)** | check if the string is a valid Brazilian RG (Registro Geral) format. The RG must follow the pattern XX.XXX.XXX-X where X represents a numeric digit (0-9). The first two digits represent the state, the next three digits represent the registration number, and the last digit is a verification digit.
**isRgbColor(str [,options])** | check if the string is a rgb or rgba color.
`options` is an object with the following properties
`includePercentValues` defaults to `true`. If you don't want to allow to set `rgb` or `rgba` values with percents, like `rgb(5%,5%,5%)`, or `rgba(90%,90%,90%,.3)`, then set it to false.
`allowSpaces` defaults to `true`, which prohibits whitespace. If set to false, whitespace between color values is allowed, such as `rgb(255, 255, 255)` or even `rgba(255, 128, 0, 0.7)`.
**isSemVer(str)** | check if the string is a Semantic Versioning Specification (SemVer).
**isSurrogatePair(str)** | check if the string contains any surrogate pairs chars.
diff --git a/src/index.js b/src/index.js
index 87be711..9548915 100644
--- a/src/index.js
+++ b/src/index.js
@@ -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';
@@ -129,6 +130,7 @@ import isLicensePlate from './lib/isLicensePlate';
import isStrongPassword from './lib/isStrongPassword';
import isVAT from './lib/isVAT';
+import isRG from './lib/isRG';
const version = '13.15.15';
@@ -179,6 +181,7 @@ const validator = {
isHexColor,
isRgbColor,
isHSL,
+ isColor,
isISRC,
isMD5,
isHash,
@@ -244,6 +247,7 @@ const validator = {
isTime,
isLicensePlate,
isVAT,
+ isRG,
ibanLocales,
};
diff --git a/src/lib/isColor.js b/src/lib/isColor.js
new file mode 100644
index 0000000..5bccc67
--- /dev/null
+++ b/src/lib/isColor.js
@@ -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);
+}
diff --git a/src/lib/isFloat.js b/src/lib/isFloat.js
index 84bdc78..3264280 100644
--- a/src/lib/isFloat.js
+++ b/src/lib/isFloat.js
@@ -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(',', '.'));
diff --git a/src/lib/isRG.js b/src/lib/isRG.js
new file mode 100644
index 0000000..49c357e
--- /dev/null
+++ b/src/lib/isRG.js
@@ -0,0 +1,8 @@
+import assertString from './util/assertString';
+
+const rgRegex = /^\d{2}\.\d{3}\.\d{3}-\d$/;
+
+export default function isRG(str) {
+ assertString(str);
+ return rgRegex.test(str);
+}
diff --git a/test/validators.test.js b/test/validators.test.js
index 12c5fc2..4723366 100644
--- a/test/validators.test.js
+++ b/test/validators.test.js
@@ -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',
@@ -15911,4 +16093,47 @@ describe('Validators', () => {
],
});
});
+
+ it('should validate RG format', () => {
+ test({
+ validator: 'isRG',
+ valid: [
+ '12.345.678-9',
+ '00.000.000-0',
+ '99.999.999-9',
+ '11.222.333-4',
+ '50.123.456-7',
+ '01.234.567-8',
+ '10.987.654-3',
+ ],
+ invalid: [
+ '12.345.678',
+ '12.345.678-',
+ '12.345.678-90',
+ '12.345.67-9',
+ '12.34.678-9',
+ '1.345.678-9',
+ '12345678-9',
+ '12.345.678.9',
+ 'AB.345.678-9',
+ '12.ABC.678-9',
+ '12.345.ABC-9',
+ '12.345.678-A',
+ '',
+ '12-345-678-9',
+ '12/345/678-9',
+ ' 12.345.678-9',
+ '12.345.678-9 ',
+ '12.345.678-9a',
+ 'a12.345.678-9',
+ '123.345.678-9',
+ '12.3456.678-9',
+ '12.345.6789-9',
+ '12.345.678-99',
+ '12..345.678-9',
+ '12.345..678-9',
+ '12.345.678--9',
+ ],
+ });
+ });
});