Skip to content
Merged
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
10 changes: 7 additions & 3 deletions packages/uniwind/src/core/native/native-utils.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,11 @@
import { formatHex, formatHex8, interpolate, parse } from 'culori'
import type { UniwindRuntime } from '../types'

export const colorMix = (color: string, mixColor: string, weight: number) => {
export const colorMix = (color: string, weight: number | string, mixColor: string) => {
const parsedWeight = typeof weight === 'string'
? parseFloat(weight) / 100
: weight

// Change alpha
if (mixColor === '#00000000') {
const parsedColor = parse(color)
Expand All @@ -12,11 +16,11 @@ export const colorMix = (color: string, mixColor: string, weight: number) => {

return formatHex8({
...parsedColor,
alpha: weight,
alpha: parsedWeight * (parsedColor.alpha ?? 1),
})
}

return formatHex(interpolate([mixColor, color])(weight))
return formatHex(interpolate([mixColor, color])(parsedWeight))
}

export function lightDark(this: UniwindRuntime, light: string, dark: string) {
Expand Down
12 changes: 10 additions & 2 deletions packages/uniwind/src/core/native/parsers/transforms.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,14 @@ const transforms = [
'perspective',
]

const processTransform = (transform: string, value: any) => {
if (transform.startsWith('scale') && typeof value === 'string') {
return parseFloat(value.replace('%', '')) / 100
}

return value
}

export const parseTransformsMutation = (styles: Record<string, any>) => {
const transformTokens = typeof styles.transform === 'string'
? styles.transform
Expand All @@ -34,13 +42,13 @@ export const parseTransformsMutation = (styles: Record<string, any>) => {

const transformValue = token.slice(transform.length + 1, -1)

transformsResult.push({ [transform]: transformValue })
transformsResult.push({ [transform]: processTransform(transform, transformValue) })
}
}

// Transforms outside of transform - { rotate: '45deg' }
if (styles[transform] !== undefined) {
transformsResult.push({ [transform]: styles[transform] })
transformsResult.push({ [transform]: processTransform(transform, styles[transform]) })
delete styles[transform]
}
}
Expand Down
2 changes: 1 addition & 1 deletion packages/uniwind/src/core/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,7 @@ export type UniwindRuntime = {
hairlineWidth: number
pixelRatio: (value: number) => number
fontScale: (value: number) => number
colorMix: (color: string, mixColor: string, weight: number) => string
colorMix: (color: string, weight: number | string, mixColor: string) => string
cubicBezier: (x1: number, y1: number, x2: number, y2: number) => string
lightDark: (light: string, dark: string) => string
}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,19 +1,7 @@
import { StyleDependency } from '../../types'
import { ProcessorBuilder } from '../processor'
import { Platform, StyleSheetTemplate } from '../types'
import { isDefined, toCamelCase } from '../utils'

const simpleSerialize = (value: any): string => {
if (Array.isArray(value)) {
return [
'[',
value.map(simpleSerialize).join(', '),
']',
].join('')
}

return JSON.stringify(value)
}
import { StyleDependency } from '../types'
import { ProcessorBuilder } from './processor'
import { Platform, StyleSheetTemplate } from './types'
import { isDefined, serialize, toCamelCase } from './utils'

const extractVarsFromString = (value: string) => {
const thisIndexes = [...value.matchAll(/this\[/g)].map(m => m.index)
Expand All @@ -27,6 +15,18 @@ const extractVarsFromString = (value: string) => {
})
}

const makeSafeForSerialization = (value: any) => {
if (value === null) {
return null
}

if (typeof value === 'string') {
return `"${value}"`
}

return value
}

export const addMetaToStylesTemplate = (Processor: ProcessorBuilder, currentPlatform: Platform) => {
const stylesheetsEntries = Object.entries(Processor.stylesheets as StyleSheetTemplate)
.map(([className, stylesPerMediaQuery]) => {
Expand All @@ -50,7 +50,7 @@ export const addMetaToStylesTemplate = (Processor: ProcessorBuilder, currentPlat

const entries = Object.entries(rest)
.flatMap(([property, value]) => Processor.RN.cssToRN(property, value))
.map(([property, value]) => [property, `function() { return ${simpleSerialize(value)} }`])
.map(([property, value]) => [`"${property}"`, `function() { return ${serialize(value)} }`])

if (platform && platform !== Platform.Native && platform !== currentPlatform) {
return null
Expand Down Expand Up @@ -103,18 +103,20 @@ export const addMetaToStylesTemplate = (Processor: ProcessorBuilder, currentPlat
entries,
minWidth,
maxWidth,
theme,
orientation,
theme: makeSafeForSerialization(theme),
orientation: makeSafeForSerialization(orientation),
rtl,
colorScheme,
colorScheme: makeSafeForSerialization(colorScheme),
native: platform !== null,
dependencies,
index,
className,
className: makeSafeForSerialization(className),
active,
focus,
disabled,
importantProperties: importantProperties?.map(property => property.startsWith('--') ? property : toCamelCase) ?? [],
importantProperties: importantProperties
?.map(property => property.startsWith('--') ? property : toCamelCase)
.map(makeSafeForSerialization) ?? [],
complexity: [
minWidth !== 0,
theme !== null,
Expand Down
9 changes: 5 additions & 4 deletions packages/uniwind/src/metro/compileVirtual.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,9 @@
import { compile } from '@tailwindcss/node'
import { addMetaToStylesTemplate } from './addMetaToStylesTemplate'
import { polyfillWeb } from './polyfillWeb'
import { ProcessorBuilder } from './processor'
import { addMetaToStylesTemplate, serializeJS } from './stylesheet'
import { Platform, Polyfills } from './types'
import { serializeJSObject } from './utils'

type CompileVirtualConfig = {
cssPath: string
Expand All @@ -28,19 +29,19 @@ export const compileVirtual = async ({ candidates, css, cssPath, platform, theme

Processor.transform(tailwindCSS)

const stylesheet = serializeJS(
const stylesheet = serializeJSObject(
addMetaToStylesTemplate(Processor, platform),
(key, value) => `"${key}": ${value}`,
)
const vars = serializeJS(
const vars = serializeJSObject(
Processor.vars,
(key, value) => `get "${key}"() { return ${value} }`,
)
const scopedVars = Object.fromEntries(
Object.entries(Processor.scopedVars)
.map(([scopedVarsName, scopedVars]) => [
scopedVarsName,
serializeJS(scopedVars, (key, value) => `get "${key}"() { return ${value} }`),
serializeJSObject(scopedVars, (key, value) => `get "${key}"() { return ${value} }`),
]),
)
const serializedScopedVars = Object.entries(scopedVars)
Expand Down
42 changes: 41 additions & 1 deletion packages/uniwind/src/metro/processor/css.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import { OverflowKeyword } from 'lightningcss'
import { Logger } from '../logger'
import { DeclarationValues } from '../types'
import { isDefined, pipe } from '../utils'
import { isDefined, pipe, roundToPrecision, shouldBeSerialized } from '../utils'
import type { ProcessorBuilder } from './processor'

export class CSS {
Expand All @@ -10,6 +10,34 @@ export class CSS {
constructor(private readonly Processor: ProcessorBuilder) {}

processValue(declarationValue: DeclarationValues): any {
const processedValue = this.getProcessedValue(declarationValue)

if (typeof processedValue === 'string') {
return this.makeSafeForSerialization(processedValue)
}

if (typeof processedValue === 'object' && processedValue !== null) {
return Object.fromEntries(
Object.entries(processedValue).map(([key, value]) => {
if (typeof value === 'string') {
return [
key,
this.makeSafeForSerialization(value),
]
}

return [
key,
value,
]
}),
)
}

return processedValue
}

private getProcessedValue(declarationValue: DeclarationValues): any {
if (typeof declarationValue !== 'object') {
return declarationValue
}
Expand Down Expand Up @@ -473,4 +501,16 @@ export class CSS {
].filter(Boolean).join(' '),
)
}

private makeSafeForSerialization(value: string) {
if (shouldBeSerialized(value)) {
return value
}

if (value.endsWith('%')) {
return `"${roundToPrecision(parseFloat(value), 2)}%"`
}

return `"${value}"`
}
}
53 changes: 25 additions & 28 deletions packages/uniwind/src/metro/processor/functions.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { CalcFor_DimensionPercentageFor_LengthValue, CalcFor_Length, CssColor, Function as FunctionType } from 'lightningcss'
import { Logger } from '../logger'
import { percentageToFloat, pipe } from '../utils'
import { pipe } from '../utils'
import type { ProcessorBuilder } from './processor'

export class Functions {
Expand Down Expand Up @@ -54,7 +54,7 @@ export class Functions {
}

if (fn.name === 'max') {
return `Math.max( ${this.Processor.CSS.processValue(fn.arguments)} )`
return `Math.max(${this.Processor.CSS.processValue(fn.arguments)})`
}

if (fn.name === 'linear-gradient') {
Expand Down Expand Up @@ -102,19 +102,19 @@ export class Functions {
}

if (['skewX', 'skewY'].includes(fn.name)) {
return `${fn.name}(${this.Processor.CSS.processValue(fn.arguments)})`
return `"${fn.name}(${this.Processor.CSS.processValue(fn.arguments)})"`
}

if (fn.name === 'hairlineWidth') {
return 'rt.hairlineWidth'
}

if (fn.name === 'pixelRatio') {
return `rt.pixelRatio( ${this.Processor.CSS.processValue(fn.arguments)} )`
return `rt.pixelRatio(${this.Processor.CSS.processValue(fn.arguments)})`
}

if (fn.name === 'fontScale') {
return `rt.fontScale( ${this.Processor.CSS.processValue(fn.arguments)} )`
return `rt.fontScale(${this.Processor.CSS.processValue(fn.arguments)})`
}

this.logger.error(`Unsupported function - ${fn.name}`)
Expand All @@ -131,17 +131,21 @@ export class Functions {
| CalcFor_Length,
) {
if (!Array.isArray(value)) {
return `Math.${name} ( ${this.processCalc(value)} )`
return `Math.${name}(${this.processCalc(value)})`
}

const values = value.map(x => this.processCalc(x)).join(' , ')

return `Math.${name}( ${values} )`
return `Math.${name}(${values})`
}

private tryEval(value: string) {
// Match units like %, deg, rad, grad, turn that are not preceded by letters or hyphens
const units = Array.from(value.match(/(?<![A-Za-z-])(?:%|deg|rad|grad|turn)(?=\s|$)/g) ?? [])
const units = Array.from(
value
.replace(/"/g, '')
.match(/(?<![A-Za-z-])(?:%|deg|rad|grad|turn)(?=\s|$)/g) ?? [],
)

if (units.length === 0) {
return value
Expand All @@ -156,7 +160,9 @@ export class Functions {
const unit = units.at(0) ?? ''

try {
const numericValue = value.replace(new RegExp(unit, 'g'), '')
const numericValue = value
.replace(/"/g, '')
.replace(new RegExp(unit, 'g'), '')

// eslint-disable-next-line @typescript-eslint/no-implied-eval, no-new-func
return new Function(`return ${numericValue} + '${unit}'`)()
Expand All @@ -168,25 +174,16 @@ export class Functions {
}

private processColorMix(fn: FunctionType) {
const tokens = pipe(this.Processor.CSS.processValue(fn.arguments))(
String,
x => x.replace(/\](?!\s*[+-/*])(?!\s)/g, '] '),
x => x.replace(/,/g, ''),
x => x.split(' '),
)
const tokens = fn.arguments
.map(arg =>
pipe(arg)(
x => this.Processor.CSS.processValue(x),
String,
x => x.trim(),
)
)
.filter(token => !['', ',', 'in', 'srgb', 'rgb', 'hsl', 'hwb', 'lab', 'lch', 'oklab', 'oklch'].includes(token.replace(/"/g, '')))

const color = tokens.find(token => token.startsWith('#') || token.startsWith('this[`--color-'))
const percentage = percentageToFloat(tokens.find(token => token.endsWith('%')) ?? '50%')
const mixColor = tokens.reverse().find(token => token.startsWith('#') || token.startsWith('this[`--color-'))

return [
'rt.colorMix( ',
color,
', ',
mixColor,
', ',
percentage,
' )',
].join('')
return `rt.colorMix(${tokens.join(', ')})`
}
}
17 changes: 1 addition & 16 deletions packages/uniwind/src/metro/processor/rn.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { addMissingSpaces, isDefined, percentageToFloat, pipe, toCamelCase } from '../utils'
import { addMissingSpaces, isDefined, pipe, toCamelCase } from '../utils'
import type { ProcessorBuilder } from './processor'

const cssToRNMap: Record<string, (value: any) => Record<string, any>> = {
Expand Down Expand Up @@ -30,21 +30,6 @@ const cssToRNMap: Record<string, (value: any) => Record<string, any>> = {
overflow: value,
}
},
'--tw-scale-x': (value: string) => {
return {
'--tw-scale-x': percentageToFloat(value),
}
},
'--tw-scale-y': (value: string) => {
return {
'--tw-scale-y': percentageToFloat(value),
}
},
'--tw-scale-z': (value: string) => {
return {
'--tw-scale-z': percentageToFloat(value),
}
},
backdropFilter: () => ({}),
backgroundImage: value => ({
experimental_backgroundImage: value,
Expand Down
2 changes: 0 additions & 2 deletions packages/uniwind/src/metro/stylesheet/index.ts

This file was deleted.

Loading