diff --git a/src/js/color.ts b/src/js/color.ts index 3e01ab6..1b9b7e9 100644 --- a/src/js/color.ts +++ b/src/js/color.ts @@ -8,7 +8,6 @@ import { createCacheKey, getCache, setCache } from './cache'; import { isString } from './common'; -import { resolveColor } from './resolve'; import { interpolateHue, roundToPrecision, splitValue } from './util'; import { ColorChannels, @@ -406,13 +405,13 @@ export const resolveInvalidColorValue = ( /** * validate color components * @param arr - color components - * @param [opt] - options - * @param [opt.alpha] - alpha channel - * @param [opt.minLength] - min length - * @param [opt.maxLength] - max length - * @param [opt.minRange] - min range - * @param [opt.maxRange] - max range - * @param [opt.validateRange] - validate range + * @param opt - options + * @param opt.alpha - alpha channel + * @param opt.minLength - min length + * @param opt.maxLength - max length + * @param opt.minRange - min range + * @param opt.maxRange - max range + * @param opt.validateRange - validate range * @returns result - validated color components */ export const validateColorComponents = ( @@ -1238,7 +1237,7 @@ export const convertHexToXyz = (value: string): ColorChannels => { /** * parse rgb() * @param value - rgb color value - * @param [opt] - options + * @param opt - options * @returns parsed color - ['rgb', r, g, b, alpha], '(empty)', null */ export const parseRgb = ( @@ -1300,7 +1299,7 @@ export const parseRgb = ( /** * parse hsl() * @param value - hsl color value - * @param [opt] - options + * @param opt - options * @returns parsed color - ['rgb', r, g, b, alpha], '(empty)', null */ export const parseHsl = ( @@ -1371,7 +1370,7 @@ export const parseHsl = ( /** * parse hwb() * @param value - hwb color value - * @param [opt] - options + * @param opt - options * @returns parsed color - ['rgb', r, g, b, alpha], '(empty)', null */ export const parseHwb = ( @@ -1442,7 +1441,7 @@ export const parseHwb = ( /** * parse lab() * @param value - lab color value - * @param [opt] - options + * @param opt - options * @returns parsed color * - [xyz-d50, x, y, z, alpha], ['lab', l, a, b, alpha], '(empty)', null */ @@ -1531,7 +1530,7 @@ export const parseLab = ( /** * parse lch() * @param value - lch color value - * @param [opt] - options + * @param opt - options * @returns parsed color * - ['xyz-d50', x, y, z, alpha], ['lch', l, c, h, alpha], '(empty)', null */ @@ -1601,7 +1600,7 @@ export const parseLch = ( /** * parse oklab() * @param value - oklab color value - * @param [opt] - options + * @param opt - options * @returns parsed color * - ['xyz-d65', x, y, z, alpha], ['oklab', l, a, b, alpha], '(empty)', null */ @@ -1675,7 +1674,7 @@ export const parseOklab = ( /** * parse oklch() * @param value - oklch color value - * @param [opt] - options + * @param opt - options * @returns parsed color * - ['xyz-d65', x, y, z, alpha], ['oklch', l, c, h, alpha], '(empty)', null */ @@ -1754,7 +1753,7 @@ export const parseOklch = ( /** * parse color() * @param value - color function value - * @param [opt] - options + * @param opt - options * @returns parsed color * - ['xyz-(d50|d65)', x, y, z, alpha], [cs, r, g, b, alpha], '(empty)', null */ @@ -1904,7 +1903,7 @@ export const parseColorFunc = ( /** * parse color value * @param value - CSS color value - * @param [opt] - options + * @param opt - options * @returns parsed color * - ['xyz-(d50|d65)', x, y, z, alpha], ['rgb', r, g, b, alpha] * - value, '(empty)', null @@ -2058,7 +2057,7 @@ export const parseColorValue = ( /** * resolve color value * @param value - CSS color value - * @param [opt] - options + * @param opt - options * @returns resolved color * - [cs, v1, v2, v3, alpha], value, '(empty)', null */ @@ -2216,7 +2215,7 @@ export const resolveColorValue = ( /** * resolve color() * @param value - color function value - * @param [opt] - options + * @param opt - options * @returns resolved color - [cs, v1, v2, v3, alpha], '(empty)', null */ export const resolveColorFunc = ( @@ -2275,7 +2274,7 @@ export const resolveColorFunc = ( /** * convert color value to linear rgb * @param value - CSS color value - * @param [opt] - options + * @param opt - options * @returns ColorChannels | null - [r, g, b, alpha] r|g|b|alpha: 0..1 */ export const convertColorToLinearRgb = ( @@ -2334,7 +2333,7 @@ export const convertColorToLinearRgb = ( /** * convert color value to rgb * @param value - CSS color value - * @param [opt] - options + * @param opt - options * @returns ColorChannels | null - [r, g, b, alpha] r|g|b: 0..255 alpha: 0..1 */ export const convertColorToRgb = ( @@ -2386,7 +2385,7 @@ export const convertColorToRgb = ( /** * convert color value to xyz * @param value - CSS color value - * @param [opt] - options + * @param opt - options * @returns ColorChannels | null - [x, y, z, alpha] */ export const convertColorToXyz = ( @@ -2441,7 +2440,7 @@ export const convertColorToXyz = ( /** * convert color value to hsl * @param value - CSS color value - * @param [opt] - options + * @param opt - options * @returns ColorChannels | null - [h, s, l, alpha], hue may be powerless */ export const convertColorToHsl = ( @@ -2491,7 +2490,7 @@ export const convertColorToHsl = ( /** * convert color value to hwb * @param value - CSS color value - * @param [opt] - options + * @param opt - options * @returns ColorChannels | null - [h, w, b, alpha], hue may be powerless */ export const convertColorToHwb = ( @@ -2541,7 +2540,7 @@ export const convertColorToHwb = ( /** * convert color value to lab * @param value - CSS color value - * @param [opt] - options + * @param opt - options * @returns ColorChannels | null - [l, a, b, alpha] */ export const convertColorToLab = ( @@ -2590,7 +2589,7 @@ export const convertColorToLab = ( /** * convert color value to lch * @param value - CSS color value - * @param [opt] - options + * @param opt - options * @returns ColorChannels | null - [l, c, h, alpha], hue may be powerless */ export const convertColorToLch = ( @@ -2639,7 +2638,7 @@ export const convertColorToLch = ( /** * convert color value to oklab * @param value - CSS color value - * @param [opt] - options + * @param opt - options * @returns ColorChannels | null - [l, a, b, alpha] */ export const convertColorToOklab = ( @@ -2683,7 +2682,7 @@ export const convertColorToOklab = ( /** * convert color value to oklch * @param value - CSS color value - * @param [opt] - options + * @param opt - options * @returns ColorChannels | null - [l, c, h, alpha], hue may be powerless */ export const convertColorToOklch = ( @@ -2727,12 +2726,14 @@ export const convertColorToOklch = ( /** * resolve color-mix() * @param value - color-mix color value - * @param [opt] - options + * @param opt - options + * @param resolver - resolver function * @returns resolved color - [cs, v1, v2, v3, alpha], '(empty)', null */ export const resolveColorMix = ( value: string, - opt: Options = {} + opt: Options = {}, + resolver: (v: string, o?: Options) => string | null = () => null ): SpecifiedColorChannels | string | null => { if (isString(value)) { value = value.toLowerCase().trim(); @@ -2766,9 +2767,13 @@ export const resolveColorMix = ( const items = value.match(REG_MIX_NEST) as RegExpMatchArray; for (const item of items) { if (item) { - let val = resolveColorMix(item, { - format: format === VAL_SPEC ? format : VAL_COMP - }) as ComputedColorChannels | string; + let val = resolveColorMix( + item, + { + format: format === VAL_SPEC ? format : VAL_COMP + }, + resolver + ) as ComputedColorChannels | string; // computed value if (Array.isArray(val)) { const [cs, v1, v2, v3, v4] = val as ComputedColorChannels; @@ -2811,10 +2816,10 @@ export const resolveColorMix = ( }); const [colorPartA = '', pctPartA = ''] = splitValue(partA); const [colorPartB = '', pctPartB = ''] = splitValue(partB); - const specifiedColorA = resolveColor(colorPartA, { + const specifiedColorA = resolver(colorPartA, { format: VAL_SPEC }) as string; - const specifiedColorB = resolveColor(colorPartB, { + const specifiedColorB = resolver(colorPartB, { format: VAL_SPEC }) as string; if (REG_MIX_IN_CS.test(csPart) && specifiedColorA && specifiedColorB) { @@ -2838,8 +2843,8 @@ export const resolveColorMix = ( .replace(colorPartB, specifiedColorB); parsed = true; } else { - const resolvedColorA = resolveColor(colorPartA, opt); - const resolvedColorB = resolveColor(colorPartB, opt); + const resolvedColorA = resolver(colorPartA, opt); + const resolvedColorB = resolver(colorPartB, opt); if (isString(resolvedColorA) && isString(resolvedColorB)) { value = value .replace(colorPartA, resolvedColorA) diff --git a/src/js/relative-color.ts b/src/js/relative-color.ts index 814ddae..826c08c 100644 --- a/src/js/relative-color.ts +++ b/src/js/relative-color.ts @@ -12,7 +12,6 @@ import { createCacheKey, getCache, setCache } from './cache'; import { NAMED_COLORS, convertColorToRgb } from './color'; import { isString, isStringOrNumber } from './common'; import { resolveDimension, serializeCalc } from './css-calc-var'; -import { resolveColor } from './resolve'; import { roundToPrecision, splitValue } from './util'; import { ColorChannels, @@ -312,12 +311,14 @@ export function resolveColorChannels( /** * extract origin color * @param value - CSS color value - * @param [opt] - options + * @param opt - options + * @param resolver - resolver function * @returns origin color value */ export function extractOriginColor( value: string, - opt: Options = {} + opt: Options = {}, + resolver: (v: string, o?: Options) => string | null = () => null ): string | null { const { colorScheme = 'normal', currentColor = '', format = '' } = opt; if (isString(value)) { @@ -361,20 +362,23 @@ export function extractOriginColor( .replace(new RegExp(`^${colorSpace}\\(`), '') .replace(/\)$/, ''); const [, originColor = ''] = splitValue(colorParts); - const specifiedOriginColor = resolveColor(originColor, { + const specifiedOriginColor = resolver(originColor, { colorScheme, format: VAL_SPEC }) as string; - if (specifiedOriginColor === '') { + if (!specifiedOriginColor) { setCache(cacheKey, null); return null; } if (format === VAL_SPEC) { value = value.replace(originColor, specifiedOriginColor); } else { - const resolvedOriginColor = resolveColor(specifiedOriginColor, opt); + const resolvedOriginColor = resolver(specifiedOriginColor, opt); if (isString(resolvedOriginColor)) { value = value.replace(originColor, resolvedOriginColor); + } else { + setCache(cacheKey, null); + return null; } } } @@ -390,7 +394,7 @@ export function extractOriginColor( return null; } } else if (format === VAL_SPEC) { - const resolvedOriginColor = resolveColor(originColor, opt); + const resolvedOriginColor = resolver(originColor, opt); if (isString(resolvedOriginColor)) { value = value.replace(originColor, resolvedOriginColor); } @@ -462,7 +466,8 @@ export function extractOriginColor( } const resolvedOriginColor = resolveRelativeColor( originColor.join('').trim(), - opt + opt, + resolver ); if (resolvedOriginColor === null) { setCache(cacheKey, null); @@ -489,12 +494,14 @@ export function extractOriginColor( /** * resolve relative color * @param value - CSS relative color value - * @param [opt] - options + * @param opt - options + * @param resolver - resolver function * @returns resolved value */ export function resolveRelativeColor( value: string, - opt: Options = {} + opt: Options = {}, + resolver: (v: string, o?: Options) => string | null = () => null ): string | null { const { format = '' } = opt; if (isString(value)) { @@ -523,7 +530,7 @@ export function resolveRelativeColor( if (cachedResult !== false) { return cachedResult.item as string | null; } - const originColor = extractOriginColor(value, opt); + const originColor = extractOriginColor(value, opt, resolver); if (originColor === null) { setCache(cacheKey, null); return null; diff --git a/src/js/resolve.ts b/src/js/resolve.ts index 329425f..b0271c0 100644 --- a/src/js/resolve.ts +++ b/src/js/resolve.ts @@ -49,7 +49,7 @@ const REG_MIX = new RegExp(SYN_MIX); /** * resolve color * @param value - CSS color value - * @param [opt] - options + * @param opt - options * @returns resolved color */ export const resolveColor = ( @@ -130,7 +130,7 @@ export const resolveColor = ( } // 3. Relative Color resolution if (REG_FN_REL.test(value)) { - const resolvedRel = resolveRelativeColor(value, opt); + const resolvedRel = resolveRelativeColor(value, opt, resolveColor); if (format === VAL_COMP) { const res = resolvedRel === null && !nullable ? RGB_TRANSPARENT : resolvedRel; @@ -184,7 +184,7 @@ export const resolveColor = ( if (currentColor) { let resolvedCurrent; if (currentColor.startsWith(FN_MIX)) { - resolvedCurrent = resolveColorMix(currentColor, opt); + resolvedCurrent = resolveColorMix(currentColor, opt, resolveColor); } else if (currentColor.startsWith(FN_COLOR)) { resolvedCurrent = resolveColorFunc(currentColor, opt); } else { @@ -206,7 +206,7 @@ export const resolveColor = ( } else if (format === VAL_SPEC) { let res = ''; if (value.startsWith(FN_MIX)) { - const mixRes = resolveColorMix(value, opt); + const mixRes = resolveColorMix(value, opt, resolveColor); res = mixRes ? (mixRes as string) : ''; } else if (value.startsWith(FN_COLOR)) { const funcRes = resolveColorFunc(value, opt); @@ -245,7 +245,7 @@ export const resolveColor = ( value = value.replace(/currentcolor/g, currentColor); } value = value.replace(/transparent/g, RGB_TRANSPARENT); - const resolvedMix = resolveColorMix(value, opt); + const resolvedMix = resolveColorMix(value, opt, resolveColor); if (resolvedMix === null) { setCache(cacheKey, null); return null; @@ -310,7 +310,7 @@ export const resolveColor = ( /** * resolve CSS color * @param value - CSS color value. system colors are not supported - * @param [opt] - options + * @param opt - options * @returns resolved value */ export const resolve = (value: string, opt: Options = {}): string | null => { @@ -321,7 +321,7 @@ export const resolve = (value: string, opt: Options = {}): string | null => { /** * is color * @param value - CSS value - * @param [opt] - options + * @param opt - options * @returns result */ export const isColor = (value: unknown, opt: Options = {}): boolean => { diff --git a/test/color.test.ts b/test/color.test.ts index a28572f..24b3fe1 100644 --- a/test/color.test.ts +++ b/test/color.test.ts @@ -7,6 +7,7 @@ import { afterEach, assert, beforeEach, describe, it } from 'vitest'; /* test */ import { genCache } from '../src/js/cache'; +import { resolveColor } from '../src/js/resolve'; import * as color from '../src/js/color'; beforeEach(() => { @@ -6803,7 +6804,24 @@ describe('convert color value to oklch', () => { }); describe('resolve color-mix()', () => { - const func = color.resolveColorMix; + const func = (val: string, opt?: any) => + color.resolveColorMix(val, opt, resolveColor); + + it('should get fallback value when resolver is not provided', () => { + const res = color.resolveColorMix( + 'color-mix(in srgb, light-dark(red, green), blue)', + { format: 'computedValue' } + ); + assert.deepEqual(res, ['rgb', 0, 0, 0, 0], 'result'); + }); + + it('should get empty string when resolver is not provided', () => { + const res = color.resolveColorMix( + 'color-mix(in srgb, light-dark(red, green), blue)', + { format: 'specifiedValue' } + ); + assert.strictEqual(res, '', 'result'); + }); it('should throw', () => { assert.throws(() => func(), TypeError, 'undefined is not a string'); diff --git a/test/relative-color.test.ts b/test/relative-color.test.ts index 7f42c91..acf7b9c 100644 --- a/test/relative-color.test.ts +++ b/test/relative-color.test.ts @@ -8,6 +8,7 @@ import { afterEach, assert, beforeEach, describe, it } from 'vitest'; /* test */ import { genCache } from '../src/js/cache'; +import { resolveColor } from '../src/js/resolve'; import * as relColor from '../src/js/relative-color'; beforeEach(() => { @@ -108,7 +109,16 @@ describe('resolve relative color channels', () => { }); describe('extract origin color', () => { - const func = relColor.extractOriginColor; + const func = (val: string, opt?: any) => + relColor.extractOriginColor(val, opt, resolveColor); + + it('should get fallback value when resolver is not provided', () => { + const res = relColor.extractOriginColor( + 'rgb(from light-dark(green, red) r g b)', + { format: 'computedValue' } + ); + assert.strictEqual(res, null, 'result'); + }); it('should get null object', () => { const res = func(); @@ -389,7 +399,45 @@ describe('extract origin color', () => { }); describe('resolve relative color', () => { - const func = relColor.resolveRelativeColor; + const func = (val: string, opt?: any) => + relColor.resolveRelativeColor(val, opt, resolveColor); + + it('should get value even without resolver', () => { + const res = relColor.resolveRelativeColor('rgb(from currentColor r g b)', { + currentColor: 'rebeccapurple' + }); + assert.strictEqual(res, 'color(srgb 0.4 0.2 0.6)', 'result'); + }); + + it('should get fallback value when resolver is not provided', () => { + const res = relColor.resolveRelativeColor('rgb(from currentColor r g b)', { + currentColor: 'unknowncolor' + }); + assert.strictEqual(res, null, 'result'); + }); + + it('should get fallback value when resolver is not provided', () => { + const res = relColor.resolveRelativeColor( + 'rgb(from light-dark(green, red) r g b)', + { format: 'computedValue' } + ); + assert.strictEqual(res, null, 'result'); + }); + + it('should get null when resolvedOriginColor is not a string', () => { + const mockResolver = (v: string, o?: any) => { + if (o?.format === 'specifiedValue') { + return 'light-dark(green, red)'; + } + return null; + }; + const res = relColor.extractOriginColor( + 'rgb(from light-dark(green, red) r g b)', + { format: 'computedValue' }, + mockResolver + ); + assert.strictEqual(res, null, 'result'); + }); it('should throw', () => { assert.throws(() => func(), TypeError, 'undefined is not a string.');