diff --git a/packages/uniwind/src/bundler/css-processor/processor.ts b/packages/uniwind/src/bundler/css-processor/processor.ts index 9831af5c..e77c2a05 100644 --- a/packages/uniwind/src/bundler/css-processor/processor.ts +++ b/packages/uniwind/src/bundler/css-processor/processor.ts @@ -311,13 +311,22 @@ export class ProcessorBuilder { if (rule.type === 'media') { const { mediaQueries } = rule.value.query + // The block's media queries have to outlive the reset between its + // sibling rules: resetting only after each parse left every rule past + // the first without the block's queries, shipping `ios:`/`android:` + // variants and later utilities of a width breakpoint unguarded to + // every platform/breakpoint. Reset to the outer config instead of a + // fresh one so media nested inside a class rule keeps writing into + // that class. + const outerConfig = this.declarationConfig + const blockMediaQueries = [...outerConfig.mediaQueries, ...mediaQueries] - this.declarationConfig.mediaQueries.push(...mediaQueries) rule.value.rules.forEach(rule => { + this.declarationConfig = { ...outerConfig, mediaQueries: blockMediaQueries } this.parseRuleRec(rule) - this.declarationConfig = this.getDeclarationConfig() }) + this.declarationConfig = outerConfig return } diff --git a/packages/uniwind/tests/native/styles-parsing/media-queries.test.ts b/packages/uniwind/tests/native/styles-parsing/media-queries.test.ts index b7142572..05829d24 100644 --- a/packages/uniwind/tests/native/styles-parsing/media-queries.test.ts +++ b/packages/uniwind/tests/native/styles-parsing/media-queries.test.ts @@ -38,3 +38,31 @@ describe('media query boundaries', () => { expect(resolveAtWidth('max-[500px]:right-[1px]', 500)).toEqual({}) }) }) + +describe('media blocks shared by multiple utilities', () => { + const originalScreen = UniwindStore.runtime.screen + + afterEach(() => { + UniwindStore.runtime.screen = originalScreen + UniwindListener.notify([StyleDependency.Dimensions]) + }) + + test('keeps every utility of a platform block on its platform', () => { + expect(resolveAtWidth('ios-block-first', 390)).toMatchObject({ paddingTop: 1 }) + expect(resolveAtWidth('ios-block-second', 390)).toMatchObject({ paddingTop: 2 }) + expect(resolveAtWidth('android-block-first', 390)).toEqual({}) + expect(resolveAtWidth('android-block-second', 390)).toEqual({}) + }) + + test('keeps every utility of a width block behind its breakpoint', () => { + expect(resolveAtWidth('wide-block-first', 390)).toEqual({}) + expect(resolveAtWidth('wide-block-second', 390)).toEqual({}) + expect(resolveAtWidth('wide-block-first', 500)).toMatchObject({ paddingTop: 4 }) + expect(resolveAtWidth('wide-block-second', 500)).toMatchObject({ paddingTop: 5 }) + }) + + test('keeps media rules nested inside a class rule attached to the class', () => { + expect(resolveAtWidth('nested-mq', 390)).toMatchObject({ paddingTop: 6 }) + expect(resolveAtWidth('nested-mq', 500)).toMatchObject({ paddingTop: 7 }) + }) +}) diff --git a/packages/uniwind/tests/test.css b/packages/uniwind/tests/test.css index 39abb952..75059d53 100644 --- a/packages/uniwind/tests/test.css +++ b/packages/uniwind/tests/test.css @@ -76,3 +76,44 @@ left: 1px; } } + +/* Platform variants are emitted by tailwind as one @media ios/android block + containing every matching utility. */ +@media ios { + .ios-block-first { + padding-top: 1px; + } + + .ios-block-second { + padding-top: 2px; + } +} + +@media android { + .android-block-first { + padding-top: 3px; + } + + .android-block-second { + padding-top: 8px; + } +} + +/* Width breakpoints group their utilities into a shared block too. */ +@media (width >= 500px) { + .wide-block-first { + padding-top: 4px; + } + + .wide-block-second { + padding-top: 5px; + } +} + +/* Media rules can also be nested inside the class rule itself. */ +.nested-mq { + padding-top: 6px; + @media (width >= 500px) { + padding-top: 7px; + } +}