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
2 changes: 1 addition & 1 deletion CONTEXT.md
Original file line number Diff line number Diff line change
Expand Up @@ -143,7 +143,7 @@ Important concepts:
- Theme variants are recognized from known theme names.
- Variant tokens (`:active`, `:focus`, `:disabled`, `:where(.theme)`, `:dir()`, `[data-x]`) are read from two selector shapes: nested under the class as `&:active` (Tailwind < 4.3.3) and flattened into the class selector as `.active\:x:active` (Tailwind >= 4.3.3). A selector carrying any token the runtime cannot observe (e.g. `[aria-disabled="true"]`, alone or stacked with a supported variant) is skipped, never applied under a weaker condition.
- Data attribute variants support boolean `data-x` and exact `data-x="value"` matching against component props.
- Media queries drive dimensions, orientation, color scheme, platform, and native/web-specific metadata.
- Media queries drive dimensions, orientation, color scheme, platform, and native/web-specific metadata. Native width bounds carry explicit exclusivity flags through compilation and resolution so `>` and `<` exclude equality without rounding fractional widths.
- Important declarations are preserved as `importantProperties`.
- Unsupported CSS features may be silently ignored on native. Prefer documenting support coverage over adding noisy runtime failures for every unsupported CSS construct.
- Tailwind composes `filter` from per-utility `--tw-*` variables and relies on `var(--x,)` empty fallbacks for unset parts, so `Var` resolves those to an empty string. Each filter function compiles to `rt.filterFn(name, amount, unit)` because `addMissingSpaces` would otherwise corrupt an inline `blur(${...}px)` template.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -64,6 +64,8 @@ export const addMetaToStylesTemplate = (Processor: ProcessorBuilder, currentPlat
orientation,
minWidth,
maxWidth,
minWidthExclusive,
maxWidthExclusive,
colorScheme,
important: _,
importantProperties,
Expand Down Expand Up @@ -113,7 +115,9 @@ export const addMetaToStylesTemplate = (Processor: ProcessorBuilder, currentPlat
}

if (
Number(minWidth) !== 0
minWidthExclusive
|| maxWidthExclusive
|| Number(minWidth) !== 0
|| Number(maxWidth) !== Number.MAX_VALUE
|| stringifiedEntries.includes('rt.screen')
) {
Expand All @@ -132,6 +136,8 @@ export const addMetaToStylesTemplate = (Processor: ProcessorBuilder, currentPlat
entries,
minWidth,
maxWidth,
minWidthExclusive,
maxWidthExclusive,
theme: makeSafeForSerialization(theme),
orientation: makeSafeForSerialization(orientation),
rtl,
Expand Down
4 changes: 4 additions & 0 deletions packages/uniwind/src/bundler/css-processor/mq.ts
Original file line number Diff line number Diff line change
Expand Up @@ -50,10 +50,12 @@ export class MQ {

if (operator === 'greater-than-equal' || operator === 'greater-than') {
mq.minWidth = result
mq.minWidthExclusive = operator === 'greater-than'
}

if (operator === 'less-than-equal' || operator === 'less-than') {
mq.maxWidth = result
mq.maxWidthExclusive = operator === 'less-than'
}
}

Expand All @@ -77,7 +79,9 @@ export class MQ {
private getInitialMediaQueryResolver(): MediaQueryResolver {
return {
minWidth: 0,
minWidthExclusive: false,
maxWidth: Number.MAX_VALUE,
maxWidthExclusive: false,
platform: null,
rtl: null,
important: false,
Expand Down
2 changes: 2 additions & 0 deletions packages/uniwind/src/bundler/css-processor/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,8 @@ import type {
export type MediaQueryResolver = {
maxWidth: any
minWidth: any
minWidthExclusive: boolean
maxWidthExclusive: boolean
platform: Platform | null
rtl: boolean | null
important: boolean
Expand Down
2 changes: 2 additions & 0 deletions packages/uniwind/src/core/native/store.ts
Original file line number Diff line number Diff line change
Expand Up @@ -144,6 +144,8 @@ class UniwindStoreBuilder {
if (
style.minWidth > this.runtime.screen.width
|| style.maxWidth < this.runtime.screen.width
|| (style.minWidthExclusive && style.minWidth === this.runtime.screen.width)
|| (style.maxWidthExclusive && style.maxWidth === this.runtime.screen.width)
|| (style.theme !== null && theme !== style.theme)
|| (style.orientation !== null && this.runtime.orientation !== style.orientation)
|| (style.rtl !== null && !this.validateDir(style.rtl, uniwindContext))
Expand Down
2 changes: 2 additions & 0 deletions packages/uniwind/src/core/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,8 @@ export type Style = {
entries: Array<[string, Var]>
minWidth: number
maxWidth: number
minWidthExclusive: boolean
maxWidthExclusive: boolean
orientation: Orientation | null
theme: ThemeName | null
rtl: boolean | null
Expand Down
65 changes: 65 additions & 0 deletions packages/uniwind/tests/native/styles-parsing/media-queries.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
import { UniwindBundlerConfig } from '../../../src/bundler/config'
import { compileNativeCSS } from '../../../src/bundler/css-compiler/compileNativeCSS'
import { Platform, StyleDependency } from '../../../src/common/consts'
import { UniwindListener } from '../../../src/core/listener'
import { UniwindStore } from '../../../src/core/native/store'
import type { GenerateStyleSheetsCallback } from '../../../src/core/types'

const resolvePadding = (width: number) => {
UniwindStore.runtime.screen = { ...UniwindStore.runtime.screen, width }
UniwindListener.notify([StyleDependency.Dimensions])

return UniwindStore.getStyles('probe', {}, {}, { scopedTheme: null, rtl: null, variables: null }).styles.paddingLeft
}

describe.each([Platform.iOS, Platform.Android])('%s media query boundaries', platform => {
const originalScreen = UniwindStore.runtime.screen

afterEach(() => {
UniwindStore.runtime.screen = originalScreen
UniwindListener.notify([StyleDependency.Dimensions])
})

const compileQuery = (query: string) => {
const config = UniwindBundlerConfig.fromMetroConfig({ cssEntryFile: './tests/test.css' }, platform)
const code = compileNativeCSS(
config,
`
.probe { padding-left: 16px; }
@media (${query}) {
.probe { padding-left: 20px; }
}
`,
)
const generate: GenerateStyleSheetsCallback = eval(`rt => ${code}`)
UniwindStore.reinit(generate, ['light', 'dark'])
}

test('an exclusive zero bound invalidates cached styles when dimensions change', () => {
compileQuery('width > 0px')

expect([0, 0.25, 0].map(resolvePadding)).toEqual([16, 20, 16])
})

test.each([
['min-width: 402px', [16, 20, 20]],
['max-width: 402px', [20, 20, 16]],
])('%s remains inclusive', (query, expected) => {
compileQuery(query)

expect([401.75, 402, 402.25].map(resolvePadding)).toEqual(expected)
})

describe.each([402, 402.5])('boundary %s', boundary => {
test.each([
['>', [16, 16, 20]],
['>=', [16, 20, 20]],
['<', [20, 16, 16]],
['<=', [20, 20, 16]],
])('width %s', (operator, expected) => {
compileQuery(`width ${operator} ${boundary}px`)

expect([boundary - 0.25, boundary, boundary + 0.25].map(resolvePadding)).toEqual(expected)
})
})
})