Skip to content

Commit 26f6f2f

Browse files
committed
Support CSS color channel calculations
1 parent 2349642 commit 26f6f2f

3 files changed

Lines changed: 42 additions & 8 deletions

File tree

packages/css-to-rn/src/colors.ts

Lines changed: 9 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -113,12 +113,17 @@ function mixRgb (
113113
const a = colorA.toRgb()
114114
const b = colorB.toRgb()
115115
const secondWeight = 1 - firstWeight
116+
const alphaValue = alpha(a.alpha * firstWeight + b.alpha * secondWeight)
117+
118+
if (alphaValue === 0) {
119+
return rgbaString({ r: 0, g: 0, b: 0, alpha: 0 })
120+
}
116121

117122
return rgbaString({
118-
r: round(a.r * firstWeight + b.r * secondWeight),
119-
g: round(a.g * firstWeight + b.g * secondWeight),
120-
b: round(a.b * firstWeight + b.b * secondWeight),
121-
alpha: alpha(a.alpha * firstWeight + b.alpha * secondWeight)
123+
r: round((a.r * a.alpha * firstWeight + b.r * b.alpha * secondWeight) / alphaValue),
124+
g: round((a.g * a.alpha * firstWeight + b.g * b.alpha * secondWeight) / alphaValue),
125+
b: round((a.b * a.alpha * firstWeight + b.b * b.alpha * secondWeight) / alphaValue),
126+
alpha: alphaValue
122127
})
123128
}
124129

packages/css-to-rn/src/values.ts

Lines changed: 20 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -272,9 +272,10 @@ function resolveCalcs (
272272
}
273273

274274
function evaluateCalc (expression: string): string | null {
275-
if (expression.includes('%')) return null
276-
const hasPx = /(?:^|[^\w.-])[+-]?(?:\d*\.)?\d+px\b/.test(expression)
277-
const normalized = expression.replace(/([+-]?(?:\d*\.)?\d+)px\b/g, '$1')
275+
const unit = getCalcUnit(expression)
276+
if (unit === false) return null
277+
278+
const normalized = expression.replace(/([+-]?(?:\d*\.)?\d+)(px\b|%)/g, (_match, number: string) => number)
278279
if (!/^[0-9+\-*/().\s]+$/.test(normalized)) return null
279280

280281
let index = 0
@@ -353,10 +354,25 @@ function evaluateCalc (expression: string): string | null {
353354
skipWhitespace()
354355

355356
return result != null && index === normalized.length && Number.isFinite(result)
356-
? hasPx ? `${result}px` : String(result)
357+
? unit ? `${roundCalc(result)}${unit}` : String(roundCalc(result))
357358
: null
358359
}
359360

361+
function getCalcUnit (expression: string): 'px' | '%' | '' | false {
362+
const units = new Set<string>()
363+
expression.replace(/(?:^|[^\w.-])[+-]?(?:\d*\.)?\d+(px\b|%)/g, (_match, unit: string) => {
364+
units.add(unit === '%' ? '%' : 'px')
365+
return ''
366+
})
367+
368+
if (units.size > 1) return false
369+
return (units.values().next().value ?? '') as 'px' | '%' | ''
370+
}
371+
372+
function roundCalc (value: number): number {
373+
return Math.round(value * 1000000) / 1000000
374+
}
375+
360376
function findMatchingParen (input: string, openIndex: number): number {
361377
let depth = 0
362378
for (let index = openIndex; index < input.length; index++) {

packages/css-to-rn/test/engine/values.test.ts

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -89,6 +89,19 @@ describe('@cssxjs/css-to-rn value resolver', () => {
8989
assert.equal(result.dependencies.dimensions, true)
9090
})
9191

92+
it('resolves percentage and unitless calc expressions for color channels', () => {
93+
assert.equal(resolveCssValue('calc(50% + 10%)').value, '60%')
94+
assert.equal(resolveCssValue('calc(0.2 * 0.5)').value, '0.1')
95+
assert.equal(resolveCssValue('oklch(calc(50% + 10%) calc(0.2 * 0.5) 250)').value, 'rgba(79, 132, 186, 1)')
96+
})
97+
98+
it('mixes srgb colors with transparent using premultiplied alpha', () => {
99+
assert.equal(
100+
resolveCssValue('color-mix(in srgb, rgb(24 107 236) 5%, transparent)').value,
101+
'rgba(24, 107, 236, 0.05)'
102+
)
103+
})
104+
92105
it('rejects unsupported calc expressions', () => {
93106
const result = resolveCssValue('calc(100% - 16px)')
94107

0 commit comments

Comments
 (0)