From 36c7975962aaa0eea57695dec708c4ef9e1e2b8e Mon Sep 17 00:00:00 2001 From: Abdul Date: Wed, 17 Nov 2021 19:09:24 +0400 Subject: [PATCH] improve injection order (#875) * improve injection order * add comments --- packages/core/src/features/css.js | 39 +++-- packages/core/src/sheet.js | 138 +++++++----------- packages/core/tests/basic.js | 2 +- packages/core/tests/component-css-prop.js | 2 +- .../core/tests/component-empty-variants.js | 2 +- packages/core/tests/component-variants.js | 22 +-- packages/core/tests/issue-450.js | 37 +++-- packages/core/tests/issue-492.js | 43 ++---- packages/react/src/features/styled.js | 6 +- .../react/tests/component-css-prop-react.js | 4 +- packages/react/tests/component-css-prop.js | 4 +- packages/react/tests/component-variants.js | 14 +- packages/react/tests/issue-416.js | 7 +- packages/react/tests/issue-671.js | 30 ++++ packages/react/tests/issue-737.js | 40 +++++ 15 files changed, 212 insertions(+), 178 deletions(-) create mode 100644 packages/react/tests/issue-671.js create mode 100644 packages/react/tests/issue-737.js diff --git a/packages/core/src/features/css.js b/packages/core/src/features/css.js index 32e6c3c0..b79bed18 100644 --- a/packages/core/src/features/css.js +++ b/packages/core/src/features/css.js @@ -7,6 +7,7 @@ import { hasOwn } from '../utility/hasOwn.js' import { toCssRules } from '../convert/toCssRules.js' import { toHash } from '../convert/toHash.js' import { toTailDashed } from '../convert/toTailDashed.js' +import { createRulesInjectionDeferrer } from '../sheet.js' /** @typedef {import('./css').Internals} Internals */ /** @typedef {import('./css').Composer} Composer */ @@ -141,6 +142,9 @@ const createRenderer = ( undefinedVariants ] = getPreparedDataFromComposers(internals.composers) + const differedInjector = typeof internals.type === 'function' ? createRulesInjectionDeferrer(sheet) : null + const injectionTarget = differedInjector?.rules || sheet.rules + const selector = `.${baseClassName}${baseClassNames.length > 1 ? `:where(.${baseClassNames.slice(1).join('.')})` : ``}` /** @type {Render} */ @@ -194,8 +198,8 @@ const createRenderer = ( if (!sheet.rules.styled.cache.has(composerBaseClass)) { sheet.rules.styled.cache.add(composerBaseClass) - toCssRules(composerBaseStyle, [`.${composerBaseClass}`], [], config, cssText => { - sheet.rules.styled.apply(cssText) + toCssRules(composerBaseStyle, [`.${composerBaseClass}`], [], config, (cssText) => { + injectionTarget.styled.apply(cssText) }) } @@ -205,16 +209,22 @@ const createRenderer = ( for (const variantToAdd of singularVariantsToAdd) { if (variantToAdd === undefined) continue - for (const [vClass, vStyle] of variantToAdd) { + for (const [vClass, vStyle, isResponsive] of variantToAdd) { const variantClassName = `${composerBaseClass}-${toHash(vStyle)}-${vClass}` classSet.add(variantClassName) - if (!sheet.rules.onevar.cache.has(variantClassName)) { - sheet.rules.onevar.cache.add(variantClassName) - - toCssRules(vStyle, [`.${variantClassName}`], [], config, cssText => { - sheet.rules.onevar.apply(cssText) + const groupCache = (isResponsive ? sheet.rules.resonevar : sheet.rules.onevar ).cache + /* + * make sure that normal variants are injected before responsive ones + * @see {@link https://github.com/modulz/stitches/issues/737|github} + */ + const targetInjectionGroup = isResponsive ? injectionTarget.resonevar : injectionTarget.onevar + + if (!groupCache.has(variantClassName)) { + groupCache.add(variantClassName) + toCssRules(vStyle, [`.${variantClassName}`], [], config, (cssText) => { + targetInjectionGroup.apply(cssText) }) } } @@ -231,8 +241,8 @@ const createRenderer = ( if (!sheet.rules.allvar.cache.has(variantClassName)) { sheet.rules.allvar.cache.add(variantClassName) - toCssRules(vStyle, [`.${variantClassName}`], [], config, cssText => { - sheet.rules.allvar.apply(cssText) + toCssRules(vStyle, [`.${variantClassName}`], [], config, (cssText) => { + injectionTarget.allvar.apply(cssText) }) } } @@ -249,8 +259,8 @@ const createRenderer = ( if (!sheet.rules.inline.cache.has(iClass)) { sheet.rules.inline.cache.add(iClass) - toCssRules(css, [`.${iClass}`], [], config, cssText => { - sheet.rules.inline.apply(cssText) + toCssRules(css, [`.${iClass}`], [], config, (cssText) => { + injectionTarget.inline.apply(cssText) }) } } @@ -269,6 +279,7 @@ const createRenderer = ( selector, props: forwardProps, toString: renderedToString, + differedInjector, } } @@ -347,6 +358,7 @@ const getTargetVariantsToAdd = ( /** @type {string & keyof typeof vMatch} */ let vName + let isResponsive = false for (vName in vMatch) { const vPair = vMatch[vName] @@ -367,6 +379,7 @@ const getTargetVariantsToAdd = ( vStyle = { [query in media ? media[query] : query]: vStyle, } + isResponsive = true } vOrder += qOrder @@ -384,7 +397,7 @@ const getTargetVariantsToAdd = ( else continue targetVariants } - (targetVariantsToAdd[vOrder] = targetVariantsToAdd[vOrder] || []).push([isCompoundVariant ? `cv` : `${vName}-${vMatch[vName]}`, vStyle]) + (targetVariantsToAdd[vOrder] = targetVariantsToAdd[vOrder] || []).push([isCompoundVariant ? `cv` : `${vName}-${vMatch[vName]}`, vStyle, isResponsive]) } return targetVariantsToAdd diff --git a/packages/core/src/sheet.js b/packages/core/src/sheet.js index 44407f88..d422ae68 100644 --- a/packages/core/src/sheet.js +++ b/packages/core/src/sheet.js @@ -2,8 +2,18 @@ /** @typedef {import('./sheet').RuleGroupNames} RuleGroupNames */ /** @typedef {import('./sheet').SheetGroup} SheetGroup */ +/** + * Rules in the sheet appear in this order: + * 1. theme rules (themed) + * 2. global rules (global) + * 3. component rules (styled) + * 4. non-responsive variants rules (onevar) + * 5. responsive variants rules (resonevar) + * 6. compound variants rules (allvar) + * 7. inline rules (inline) + */ /** @type {RuleGroupNames} */ -const names = ['themed', 'global', 'styled', 'onevar', 'allvar', 'inline'] +export const names = ['themed', 'global', 'styled', 'onevar', 'resonevar', 'allvar', 'inline'] export const createSheet = (/** @type {DocumentOrShadowRoot} */ root) => { /** @type {SheetGroup} Object hosting the hydrated stylesheet. */ @@ -22,10 +32,6 @@ export const createSheet = (/** @type {DocumentOrShadowRoot} */ root) => { for (const groupName in rules) { delete rules[groupName] } - - if (sheet.ownerRule) { - sheet.ownerRule.textContent = sheet.ownerRule.textContent - } } /** @type {StyleSheetList} */ @@ -123,90 +129,22 @@ export const createSheet = (/** @type {DocumentOrShadowRoot} */ root) => { } const { sheet, rules } = groupSheet - - // conditionally generate the inline group - if (!rules.inline) { - const index = sheet.cssRules.length - sheet.insertRule('@media{}', index) - sheet.insertRule('--sxs{--sxs:5}', index) - - rules.inline = { - index: index, - group: sheet.cssRules[index + 1], - cache: new Set([5]), - } - } - addApplyToGroup(rules.inline) - - // conditionally generate the allvar group - if (!rules.allvar) { - const index = rules.inline.index - sheet.insertRule('@media{}', index) - sheet.insertRule('--sxs{--sxs:4}', index) - - rules.allvar = { - index: index, - group: sheet.cssRules[index + 1], - cache: new Set([4]), - } - } - addApplyToGroup(rules.allvar) - - // conditionally generate the onevar group - if (!rules.onevar) { - const index = rules.allvar.index - sheet.insertRule('@media{}', index) - sheet.insertRule('--sxs{--sxs:3}', index) - - rules.onevar = { - index: index, - group: sheet.cssRules[index + 1], - cache: new Set([3]), - } - } - addApplyToGroup(rules.onevar) - - // conditionally generate the styled group - if (!rules.styled) { - const index = rules.onevar.index - sheet.insertRule('@media{}', index) - sheet.insertRule('--sxs{--sxs:2}', index) - - rules.styled = { - index: index, - group: sheet.cssRules[index + 1], - cache: new Set([2]), - } - } - addApplyToGroup(rules.styled) - - // conditionally generate the global group - if (!rules.global) { - const index = rules.styled.index - sheet.insertRule('@media{}', index) - sheet.insertRule('--sxs{--sxs:1}', index) - - rules.global = { - index: index, - group: sheet.cssRules[index + 1], - cache: new Set([1]), - } - } - addApplyToGroup(rules.global) - - // conditionally generate the themed group - if (!rules.themed) { - const index = rules.global.index - sheet.insertRule('@media{}', index) - sheet.insertRule('--sxs{--sxs:0}', index) - - rules.themed = { - index: index, - group: sheet.cssRules[index + 1], - cache: new Set([0]), + for (let i = names.length - 1; i >= 0; --i) { + // name of group on current index + const name = names[i] + if (!rules[name]) { + // name of prev group + const prevName = names[i + 1] + // get the index of that prev group or else get the length of the whole sheet + const index = rules[prevName] ? rules[prevName].index : sheet.cssRules.length + // insert the grouping & the sxs rule + sheet.insertRule('@media{}', index) + sheet.insertRule(`--sxs{--sxs:${i}}`, index) + // add the group to the group sheet + rules[name] = { group: sheet.cssRules[index + 1], index, cache: new Set([i]) } } + addApplyToGroup(rules[name]) } - addApplyToGroup(rules.themed) } reset() @@ -229,3 +167,29 @@ const addApplyToGroup = (/** @type {RuleGroup} */ group) => { } } } +/** Pending rules for injection */ +const $pr = Symbol() + +/** + * When a stitches component is extending some other random react component, + * it’s gonna create a react component (Injector) using this function and then render it after the children, + * this way, we would force the styles of the wrapper to be injected after the wrapped component + */ +export const createRulesInjectionDeferrer = (globalSheet) => { + // the injection deferrer + function injector() { + for (let i = 0; i < injector[$pr].length; i++) { + const [sheet, cssString] = injector[$pr][i] + globalSheet.rules[sheet].apply(cssString) + } + injector[$pr] = [] + return null + } + // private prop to store pending rules + injector[$pr] = [] + // mocking the rules.apply api used on the sheet + injector.rules = {} + // creating the apply methods under rules[something] + names.forEach((sheetName) => (injector.rules[sheetName] = { apply: (rule) => injector[$pr].push([sheetName, rule]) })) + return injector +} diff --git a/packages/core/tests/basic.js b/packages/core/tests/basic.js index 64ad9ea4..e8409f61 100644 --- a/packages/core/tests/basic.js +++ b/packages/core/tests/basic.js @@ -119,7 +119,7 @@ describe('Basic', () => { `--sxs{--sxs:2 c-dataoT}@media{` + `.c-dataoT{color:DodgerBlue}` + `}` + - `--sxs{--sxs:5 c-dataoT-icaIZdx-css}@media{` + + `--sxs{--sxs:6 c-dataoT-icaIZdx-css}@media{` + `.c-dataoT-icaIZdx-css{color:Crimson}` + `}` ) diff --git a/packages/core/tests/component-css-prop.js b/packages/core/tests/component-css-prop.js index d95cbb2a..c3328c05 100644 --- a/packages/core/tests/component-css-prop.js +++ b/packages/core/tests/component-css-prop.js @@ -22,7 +22,7 @@ describe('Component with CSS prop', () => { `--sxs{--sxs:2 c-hhyRYU}@media{` + `.c-hhyRYU{order:1}` + `}` + - `--sxs{--sxs:5 c-hhyRYU-ilhKMMn-css}@media{` + + `--sxs{--sxs:6 c-hhyRYU-ilhKMMn-css}@media{` + `.c-hhyRYU-ilhKMMn-css{order:2}` + `}` ) diff --git a/packages/core/tests/component-empty-variants.js b/packages/core/tests/component-empty-variants.js index f6f69a00..91a57cfb 100644 --- a/packages/core/tests/component-empty-variants.js +++ b/packages/core/tests/component-empty-variants.js @@ -52,7 +52,7 @@ describe('Empty Variants', () => { }) expect(getCssText()).toBe( - `--sxs{--sxs:4 c-PJLV-lhHHWD-cv}@media{` + + `--sxs{--sxs:5 c-PJLV-lhHHWD-cv}@media{` + `.c-PJLV-lhHHWD-cv{font-size:24px;color:black}` + `}` ) diff --git a/packages/core/tests/component-variants.js b/packages/core/tests/component-variants.js index 56fbee04..5b69c9c4 100644 --- a/packages/core/tests/component-variants.js +++ b/packages/core/tests/component-variants.js @@ -113,7 +113,7 @@ describe('Variants', () => { expect(getCssText()).toBe( `--sxs{--sxs:3 c-PJLV-kaCQqN-color-blue c-PJLV-Gaggi-size-small}@media{${ expressionColorBlueCssText + expressionSizeSmallCssText - }}--sxs{--sxs:4 c-PJLV-cChFtv-cv}@media{${ + }}--sxs{--sxs:5 c-PJLV-cChFtv-cv}@media{${ expressionCompoundCssText }}` ) @@ -219,7 +219,7 @@ describe('Variants with defaults', () => { `.c-PJLV-kaCQqN-color-blue{background-color:dodgerblue;color:white}` + // implicit size:small `.c-PJLV-Gaggi-size-small{font-size:16px}` + - `}--sxs{--sxs:4 c-PJLV-cChFtv-cv}@media{` + + `}--sxs{--sxs:5 c-PJLV-cChFtv-cv}@media{` + // compound color:blue + size:small `.c-PJLV-cChFtv-cv{transform:scale(1.2)}` + `}`, @@ -311,7 +311,7 @@ describe('Conditional variants', () => { expect(component({ size: { '@bp1': 'small' } }).className).toBe(`c-PJLV c-PJLV-iVKIeV-size-small`) expect(getCssText()).toBe( - `--sxs{--sxs:3 c-PJLV-iVKIeV-size-small}@media{` + + `--sxs{--sxs:4 c-PJLV-iVKIeV-size-small}@media{` + `@media (max-width: 767px){.c-PJLV-iVKIeV-size-small{font-size:16px}}` + `}` ) @@ -328,7 +328,7 @@ describe('Conditional variants', () => { expect(component({ size: { '@bp1': 'small', '@bp2': 'large' } }).className).toBe([componentClassName, componentSmallBp1ClassName, componentLargeBp2ClassName].join(' ')) expect(getCssText()).toBe( - `--sxs{--sxs:3 c-PJLV-iVKIeV-size-small c-PJLV-bUkcYv-size-large}@media{` + + `--sxs{--sxs:4 c-PJLV-iVKIeV-size-small c-PJLV-bUkcYv-size-large}@media{` + componentSmallBp1CssText + componentLargeBp2CssText + `}` @@ -341,7 +341,7 @@ describe('Conditional variants', () => { expect(component({ size: { '@bp1': 'small', '@bp2': 'large' } }).className).toBe(`c-PJLV c-PJLV-iVKIeV-size-small c-PJLV-bUkcYv-size-large`) expect(getCssText()).toBe( - `--sxs{--sxs:3 c-PJLV-iVKIeV-size-small c-PJLV-bUkcYv-size-large}@media{` + + `--sxs{--sxs:4 c-PJLV-iVKIeV-size-small c-PJLV-bUkcYv-size-large}@media{` + `@media (max-width: 767px){.c-PJLV-iVKIeV-size-small{font-size:16px}}` + `@media (min-width: 768px){.c-PJLV-bUkcYv-size-large{font-size:24px}}` + `}` @@ -349,7 +349,7 @@ describe('Conditional variants', () => { expect(component({ size: { '@bp1': 'small', '@bp2': 'large' } }).className).toBe(`c-PJLV c-PJLV-iVKIeV-size-small c-PJLV-bUkcYv-size-large`) expect(getCssText()).toBe( - `--sxs{--sxs:3 c-PJLV-iVKIeV-size-small c-PJLV-bUkcYv-size-large}@media{` + + `--sxs{--sxs:4 c-PJLV-iVKIeV-size-small c-PJLV-bUkcYv-size-large}@media{` + `@media (max-width: 767px){.c-PJLV-iVKIeV-size-small{font-size:16px}}` + `@media (min-width: 768px){.c-PJLV-bUkcYv-size-large{font-size:24px}}` + `}` @@ -357,7 +357,7 @@ describe('Conditional variants', () => { expect(component({ size: { '@bp1': 'small', '@bp2': 'large' } }).className).toBe(`c-PJLV c-PJLV-iVKIeV-size-small c-PJLV-bUkcYv-size-large`) expect(getCssText()).toBe( - `--sxs{--sxs:3 c-PJLV-iVKIeV-size-small c-PJLV-bUkcYv-size-large}@media{` + + `--sxs{--sxs:4 c-PJLV-iVKIeV-size-small c-PJLV-bUkcYv-size-large}@media{` + `@media (max-width: 767px){.c-PJLV-iVKIeV-size-small{font-size:16px}}` + `@media (min-width: 768px){.c-PJLV-bUkcYv-size-large{font-size:24px}}` + `}` @@ -390,7 +390,7 @@ describe('Conditional variants', () => { ).toBe('c-PJLV c-PJLV-gjWYHE-size-small c-PJLV-fzmUzy-size-large') expect(getCssText()).toBe( - `--sxs{--sxs:3 c-PJLV-gjWYHE-size-small c-PJLV-fzmUzy-size-large}@media{` + + `--sxs{--sxs:4 c-PJLV-gjWYHE-size-small c-PJLV-fzmUzy-size-large}@media{` + `@media (max-width:767.9375px){.c-PJLV-gjWYHE-size-small{font-size:16px}}` + `@media (min-width:768px){.c-PJLV-fzmUzy-size-large{font-size:24px}}` + `}` @@ -422,7 +422,7 @@ describe('Conditional variants', () => { ).toBe('c-PJLV c-PJLV-gjWYHE-size-small c-PJLV-fzmUzy-size-large') expect(getCssText()).toBe( - `--sxs{--sxs:3 c-PJLV-gjWYHE-size-small c-PJLV-fzmUzy-size-large}@media{` + + `--sxs{--sxs:4 c-PJLV-gjWYHE-size-small c-PJLV-fzmUzy-size-large}@media{` + `@media (max-width:767.9375px){.c-PJLV-gjWYHE-size-small{font-size:16px}}` + `@media (min-width:768px){.c-PJLV-fzmUzy-size-large{font-size:24px}}` + `}` @@ -499,7 +499,7 @@ describe('Variant pairing types', () => { `--sxs{--sxs:2 c-foEXqW}@media{` + `.c-foEXqW{--component:true}` + `}` + - `--sxs{--sxs:3 c-foEXqW-brOaTK-testBoolean-true}@media{` + + `--sxs{--sxs:4 c-foEXqW-brOaTK-testBoolean-true}@media{` + `@media (min-width: 640px){` + `.c-foEXqW-brOaTK-testBoolean-true{--test-boolean:true}` + `}` + @@ -517,7 +517,7 @@ describe('Variant pairing types', () => { `--sxs{--sxs:2 c-foEXqW}@media{` + `.c-foEXqW{--component:true}` + `}` + - `--sxs{--sxs:3 c-foEXqW-brOaTK-testBoolean-true}@media{` + + `--sxs{--sxs:4 c-foEXqW-brOaTK-testBoolean-true}@media{` + `@media (min-width: 640px){` + `.c-foEXqW-brOaTK-testBoolean-true{--test-boolean:true}` + `}` + diff --git a/packages/core/tests/issue-450.js b/packages/core/tests/issue-450.js index 044c2f2a..b6677744 100644 --- a/packages/core/tests/issue-450.js +++ b/packages/core/tests/issue-450.js @@ -77,7 +77,7 @@ describe('Issue #450', () => { const { component1, getCssText } = getFreshComponents() const render = component1({ color: { '@media (min-width: 640px)': 'blue' } }) expect(render.className).toBe(`c-PJLV c-PJLV-gmqXFB-color-red c-PJLV-bBevdw-color-blue`) - expect(getCssText()).toBe(`--sxs{--sxs:3 c-PJLV-gmqXFB-color-red c-PJLV-bBevdw-color-blue}@media{.c-PJLV-gmqXFB-color-red{color:red}@media (min-width: 640px){.c-PJLV-bBevdw-color-blue{color:blue}}}`) + expect(getCssText()).toBe(`--sxs{--sxs:3 c-PJLV-gmqXFB-color-red}@media{.c-PJLV-gmqXFB-color-red{color:red}}--sxs{--sxs:4 c-PJLV-bBevdw-color-blue}@media{@media (min-width: 640px){.c-PJLV-bBevdw-color-blue{color:blue}}}`) }) test('Render component2() as orange, inherited from defaultVariants', () => { @@ -93,10 +93,7 @@ describe('Issue #450', () => { const render = component2({ color: { '@media (min-width: 640px)': 'blue' } }) expect(render.className).toBe(`c-PJLV c-PJLV-bBevdw-color-blue c-PJLV-vMTTG-color-orange`) expect(getCssText()).toBe( - `--sxs{--sxs:3 c-PJLV-bBevdw-color-blue c-PJLV-vMTTG-color-orange}@media{` + - `@media (min-width: 640px){.c-PJLV-bBevdw-color-blue{color:blue}}` + - `.c-PJLV-vMTTG-color-orange{color:orange}` + - `}` + `--sxs{--sxs:3 c-PJLV-vMTTG-color-orange}` + `@media{.c-PJLV-vMTTG-color-orange{color:orange}}` + `--sxs{--sxs:4 c-PJLV-bBevdw-color-blue}@media{` + `@media (min-width: 640px){.c-PJLV-bBevdw-color-blue{color:blue}}` + `}`, ) }) }) @@ -190,14 +187,14 @@ describe('Issue #450', () => { expect(getCssText()).toBe( `--sxs{--sxs:2 c-jyxqjt}@media{` + `.c-jyxqjt{--component:1}` + - `}` + - `--sxs{--sxs:3 c-jyxqjt-cOChOn-appearance-secondary c-jyxqjt-ilDyRi-color-lightBlue}@media{` + + `}` + + `--sxs{--sxs:3 c-jyxqjt-cOChOn-appearance-secondary c-jyxqjt-ilDyRi-color-lightBlue}@media{` + `.c-jyxqjt-cOChOn-appearance-secondary{--appearance:secondary}` + `.c-jyxqjt-ilDyRi-color-lightBlue{--color:lightBlue}` + - `}` + - `--sxs{--sxs:4 c-jyxqjt-gYqlvA-cv}@media{` + + `}` + + `--sxs{--sxs:5 c-jyxqjt-gYqlvA-cv}@media{` + `.c-jyxqjt-gYqlvA-cv{--compound:appearance secondary / color lightBlue}` + - `}` + `}`, ) }) @@ -209,14 +206,14 @@ describe('Issue #450', () => { `--sxs{--sxs:2 c-jyxqjt c-dkRcuu}@media{` + `.c-jyxqjt{--component:1}` + `.c-dkRcuu{--component:2}` + - `}` + - `--sxs{--sxs:3 c-jyxqjt-cOChOn-appearance-secondary c-jyxqjt-ilDyRi-color-lightBlue}@media{` + + `}` + + `--sxs{--sxs:3 c-jyxqjt-cOChOn-appearance-secondary c-jyxqjt-ilDyRi-color-lightBlue}@media{` + `.c-jyxqjt-cOChOn-appearance-secondary{--appearance:secondary}` + `.c-jyxqjt-ilDyRi-color-lightBlue{--color:lightBlue}` + - `}` + - `--sxs{--sxs:4 c-jyxqjt-gYqlvA-cv}@media{` + + `}` + + `--sxs{--sxs:5 c-jyxqjt-gYqlvA-cv}@media{` + `.c-jyxqjt-gYqlvA-cv{--compound:appearance secondary / color lightBlue}` + - `}` + `}`, ) }) @@ -228,14 +225,14 @@ describe('Issue #450', () => { `--sxs{--sxs:2 c-jyxqjt c-dkRcuu}@media{` + `.c-jyxqjt{--component:1}` + `.c-dkRcuu{--component:2}` + - `}` + - `--sxs{--sxs:3 c-jyxqjt-cOChOn-appearance-secondary c-jyxqjt-ilDyRi-color-lightBlue}@media{` + + `}` + + `--sxs{--sxs:3 c-jyxqjt-cOChOn-appearance-secondary c-jyxqjt-ilDyRi-color-lightBlue}@media{` + `.c-jyxqjt-cOChOn-appearance-secondary{--appearance:secondary}` + `.c-jyxqjt-ilDyRi-color-lightBlue{--color:lightBlue}` + - `}` + - `--sxs{--sxs:4 c-jyxqjt-gYqlvA-cv}@media{` + + `}` + + `--sxs{--sxs:5 c-jyxqjt-gYqlvA-cv}@media{` + `.c-jyxqjt-gYqlvA-cv{--compound:appearance secondary / color lightBlue}` + - `}` + `}`, ) }) }) diff --git a/packages/core/tests/issue-492.js b/packages/core/tests/issue-492.js index 17d59d9f..ee6038f9 100644 --- a/packages/core/tests/issue-492.js +++ b/packages/core/tests/issue-492.js @@ -57,13 +57,12 @@ describe('Issue #492', () => { `${componentClassName} ${variantSweetCarolineClassName} ${variantResponsiveSweetDreamsClassName}` ) - expect( - getCssText() - ).toBe( - `--sxs{--sxs:3 ${variantSweetCarolineClassName} ${variantResponsiveSweetDreamsClassName}}@media{` + - `.${variantSweetCarolineClassName}{--sweet-caroline:true}` + + expect(getCssText()).toBe( + `--sxs{--sxs:3 ${variantSweetCarolineClassName}}` + + `@media{.${variantSweetCarolineClassName}{--sweet-caroline:true}}` + + `--sxs{--sxs:4 ${variantResponsiveSweetDreamsClassName}}@media{` + `@media (min-width: 640px){.${variantResponsiveSweetDreamsClassName}{--sweet-dreams:true}}` + - `}` + `}`, ) /** Rendering of the component as-is. */ @@ -74,23 +73,17 @@ describe('Issue #492', () => { }, }) - expect( - rendering3.className - ).toBe( - `${componentClassName} ${variantSweetDreamsClassName} ${variantResponsiveSweetCarolineClassName}` - ) + expect(rendering3.className).toBe(`${componentClassName} ${variantSweetDreamsClassName} ${variantResponsiveSweetCarolineClassName}`) - expect( - getCssText() - ).toBe( - `--sxs{--sxs:3 ${variantSweetCarolineClassName} ${variantResponsiveSweetDreamsClassName} ${variantSweetDreamsClassName} ${variantResponsiveSweetCarolineClassName}}@media{` + - // last rendering - `.${variantSweetCarolineClassName}{--sweet-caroline:true}` + + expect(getCssText()).toBe( + // initial variants + `--sxs{--sxs:3 ${variantSweetCarolineClassName} ${variantSweetDreamsClassName}}` + + `@media{.${variantSweetCarolineClassName}{--sweet-caroline:true}.${variantSweetDreamsClassName}{--sweet-dreams:true}}` + + // responsive variants + `--sxs{--sxs:4 ${variantResponsiveSweetDreamsClassName} ${variantResponsiveSweetCarolineClassName}}@media{` + `@media (min-width: 640px){.${variantResponsiveSweetDreamsClassName}{--sweet-dreams:true}}` + - // this rendering - `.${variantSweetDreamsClassName}{--sweet-dreams:true}` + `@media (min-width: 640px){.${variantResponsiveSweetCarolineClassName}{--sweet-caroline:true}}` + - `}` + `}`, ) }) @@ -131,14 +124,6 @@ describe('Issue #492', () => { expect( getCssText() - ).toBe( - `--sxs{--sxs:2 ${componentClassName}}@media{` + - `.${componentClassName}{--rock:true}` + - `}` + - `--sxs{--sxs:3 ${variantInitialHeavyIronButterfly} ${variantMinWidth640LedZeppelin}}@media{` + - `.${variantInitialHeavyIronButterfly}{--weight-iron-butterfly:true}` + - `@media (min-width: 640px){.${variantMinWidth640LedZeppelin}{--weight-led-zeppelin:true}}` + - `}` - ) + ).toBe('--sxs{--sxs:2 c-evVBJo}@media{.c-evVBJo{--rock:true}}--sxs{--sxs:3 c-evVBJo-kiVNrc-heavy-iron-butterfly}@media{.c-evVBJo-kiVNrc-heavy-iron-butterfly{--weight-iron-butterfly:true}}--sxs{--sxs:4 c-evVBJo-lgYcvN-heavy-led-zeppelin}@media{@media (min-width: 640px){.c-evVBJo-lgYcvN-heavy-led-zeppelin{--weight-led-zeppelin:true}}}') }) }) diff --git a/packages/react/src/features/styled.js b/packages/react/src/features/styled.js index a7724438..8fb2517c 100644 --- a/packages/react/src/features/styled.js +++ b/packages/react/src/features/styled.js @@ -22,12 +22,16 @@ export const createStyledFunction = ({ /** @type {Config} */ config, /** @type { const styledComponent = React.forwardRef((props, ref) => { const Type = props && props.as || DefaultType - const forwardProps = cssComponent(props).props + const { props: forwardProps, differedInjector } = cssComponent(props) delete forwardProps.as forwardProps.ref = ref + if (differedInjector) { + return React.createElement(React.Fragment, null, React.createElement(Type, forwardProps), React.createElement(differedInjector, null)) + } + return React.createElement(Type, forwardProps) }) diff --git a/packages/react/tests/component-css-prop-react.js b/packages/react/tests/component-css-prop-react.js index 583dcacf..01e64e8f 100644 --- a/packages/react/tests/component-css-prop-react.js +++ b/packages/react/tests/component-css-prop-react.js @@ -86,7 +86,7 @@ describe('React Component with CSS prop', () => { }) expect(toString()).toBe( - `--sxs{--sxs:2 c-bMUtqP}@media{.c-bMUtqP{line-height:1;margin:0;font-weight:400;font-variant-numeric:tabular-nums;display:block}}--sxs{--sxs:5 c-bMUtqP-ieTXEfC-css}@media{.c-bMUtqP-ieTXEfC-css{font-weight:500;font-variant-numeric:proportional-nums;line-height:35px;text-align:center;margin-bottom:var(--space-3)}@media (min-width: 900px){.c-bMUtqP-ieTXEfC-css{line-height:55px;color:red}}}`, + `--sxs{--sxs:2 c-bMUtqP}@media{.c-bMUtqP{line-height:1;margin:0;font-weight:400;font-variant-numeric:tabular-nums;display:block}}--sxs{--sxs:6 c-bMUtqP-ieTXEfC-css}@media{.c-bMUtqP-ieTXEfC-css{font-weight:500;font-variant-numeric:proportional-nums;line-height:35px;text-align:center;margin-bottom:var(--space-3)}@media (min-width: 900px){.c-bMUtqP-ieTXEfC-css{line-height:55px;color:red}}}`, ) // ... @@ -105,7 +105,7 @@ describe('React Component with CSS prop', () => { }) expect(toString()).toBe( - `--sxs{--sxs:2 c-bMUtqP c-dnnagC}@media{.c-bMUtqP{line-height:1;margin:0;font-weight:400;font-variant-numeric:tabular-nums;display:block}.c-dnnagC .c-bMUtqP{color:inherit}}--sxs{--sxs:5 c-bMUtqP-ieTXEfC-css}@media{.c-bMUtqP-ieTXEfC-css{font-weight:500;font-variant-numeric:proportional-nums;line-height:35px;text-align:center;margin-bottom:var(--space-3)}@media (min-width: 900px){.c-bMUtqP-ieTXEfC-css{line-height:55px;color:red}}}`, + `--sxs{--sxs:2 c-bMUtqP c-dnnagC}@media{.c-bMUtqP{line-height:1;margin:0;font-weight:400;font-variant-numeric:tabular-nums;display:block}.c-dnnagC .c-bMUtqP{color:inherit}}--sxs{--sxs:6 c-bMUtqP-ieTXEfC-css}@media{.c-bMUtqP-ieTXEfC-css{font-weight:500;font-variant-numeric:proportional-nums;line-height:35px;text-align:center;margin-bottom:var(--space-3)}@media (min-width: 900px){.c-bMUtqP-ieTXEfC-css{line-height:55px;color:red}}}`, ) }) }) diff --git a/packages/react/tests/component-css-prop.js b/packages/react/tests/component-css-prop.js index e41965c9..e8db2047 100644 --- a/packages/react/tests/component-css-prop.js +++ b/packages/react/tests/component-css-prop.js @@ -12,7 +12,7 @@ describe('React Component with CSS prop', () => { }, }) - expect(toString()).toBe(`--sxs{--sxs:2 c-hhyRYU}@media{.c-hhyRYU{order:1}}--sxs{--sxs:5 c-hhyRYU-ilhKMMn-css}@media{.c-hhyRYU-ilhKMMn-css{order:2}}`) + expect(toString()).toBe(`--sxs{--sxs:2 c-hhyRYU}@media{.c-hhyRYU{order:1}}--sxs{--sxs:6 c-hhyRYU-ilhKMMn-css}@media{.c-hhyRYU-ilhKMMn-css{order:2}}`) }) test('React example from Radix', () => { @@ -41,7 +41,7 @@ describe('React Component with CSS prop', () => { }) expect(toString()).toBe( - `--sxs{--sxs:2 c-bHwuwj}@media{.c-bHwuwj{color:inherit}}--sxs{--sxs:5 c-bHwuwj-ibwrayD-css}@media{.c-bHwuwj-ibwrayD-css{font-weight:500;font-variant-numeric:proportional-nums;line-height:35px}@media (min-width: 900px){.c-bHwuwj-ibwrayD-css{line-height:55px;color:red}}}`, + `--sxs{--sxs:2 c-bHwuwj}@media{.c-bHwuwj{color:inherit}}--sxs{--sxs:6 c-bHwuwj-ibwrayD-css}@media{.c-bHwuwj-ibwrayD-css{font-weight:500;font-variant-numeric:proportional-nums;line-height:35px}@media (min-width: 900px){.c-bHwuwj-ibwrayD-css{line-height:55px;color:red}}}`, ) }) }) diff --git a/packages/react/tests/component-variants.js b/packages/react/tests/component-variants.js index 126cb4a2..e276c974 100644 --- a/packages/react/tests/component-variants.js +++ b/packages/react/tests/component-variants.js @@ -95,7 +95,7 @@ describe('Variants', () => { `--sxs{--sxs:3 c-PJLV-kaCQqN-color-blue c-PJLV-Gaggi-size-small}@media{` + expressionColorBlueCssText + expressionSizeSmallCssText + - `}--sxs{--sxs:4 c-PJLV-cChFtv-cv}@media{` + + `}--sxs{--sxs:5 c-PJLV-cChFtv-cv}@media{` + expressionCompoundCssText + `}` ) @@ -201,7 +201,7 @@ describe('Variants with defaults', () => { `.c-PJLV-kaCQqN-color-blue{background-color:dodgerblue;color:white}` + // implicit size:small `.c-PJLV-Gaggi-size-small{font-size:16px}` + - `}--sxs{--sxs:4 c-PJLV-cChFtv-cv}@media{` + + `}--sxs{--sxs:5 c-PJLV-cChFtv-cv}@media{` + // compound color:blue + size:small `.c-PJLV-cChFtv-cv{transform:scale(1.2)}` + `}` @@ -296,7 +296,7 @@ describe('Conditional variants', () => { expect(component.render({ size: { '@bp1': 'small' } }).props.className).toBe([componentClassName, componentSmallBp1ClassName].join(' ')) expect(toString()).toBe( - `--sxs{--sxs:3 c-PJLV-iVKIeV-size-small}@media{` + + `--sxs{--sxs:4 c-PJLV-iVKIeV-size-small}@media{` + componentSmallBp1CssText + `}` ) @@ -313,7 +313,7 @@ describe('Conditional variants', () => { expect(component.render({ size: { '@bp1': 'small', '@bp2': 'large' } }).props.className).toBe([componentClassName, componentSmallBp1ClassName, componentLargeBp2ClassName].join(' ')) expect(toString()).toBe( - `--sxs{--sxs:3 c-PJLV-iVKIeV-size-small c-PJLV-bUkcYv-size-large}@media{` + + `--sxs{--sxs:4 c-PJLV-iVKIeV-size-small c-PJLV-bUkcYv-size-large}@media{` + componentSmallBp1CssText + componentLargeBp2CssText + `}` @@ -331,7 +331,7 @@ describe('Conditional variants', () => { expect(component.render({ size: { '@bp1': 'small', '@bp2': 'large' } }).props.className).toBe([componentClassName, componentSmallBp1ClassName, componentLargeBp2ClassName].join(' ')) expect(toString()).toBe( - `--sxs{--sxs:3 c-PJLV-iVKIeV-size-small c-PJLV-bUkcYv-size-large}@media{` + + `--sxs{--sxs:4 c-PJLV-iVKIeV-size-small c-PJLV-bUkcYv-size-large}@media{` + componentSmallBp1CssText + componentLargeBp2CssText + `}` @@ -339,7 +339,7 @@ describe('Conditional variants', () => { expect(component.render({ size: { '@bp1': 'small', '@bp2': 'large' } }).props.className).toBe([componentClassName, componentSmallBp1ClassName, componentLargeBp2ClassName].join(' ')) expect(toString()).toBe( - `--sxs{--sxs:3 c-PJLV-iVKIeV-size-small c-PJLV-bUkcYv-size-large}@media{` + + `--sxs{--sxs:4 c-PJLV-iVKIeV-size-small c-PJLV-bUkcYv-size-large}@media{` + `@media (max-width: 767px){.c-PJLV-iVKIeV-size-small{font-size:16px}}` + `@media (min-width: 768px){.c-PJLV-bUkcYv-size-large{font-size:24px}}` + `}` @@ -347,7 +347,7 @@ describe('Conditional variants', () => { expect(component.render({ size: { '@bp1': 'small', '@bp2': 'large' } }).props.className).toBe(`c-PJLV c-PJLV-iVKIeV-size-small c-PJLV-bUkcYv-size-large`) expect(toString()).toBe( - `--sxs{--sxs:3 c-PJLV-iVKIeV-size-small c-PJLV-bUkcYv-size-large}@media{` + + `--sxs{--sxs:4 c-PJLV-iVKIeV-size-small c-PJLV-bUkcYv-size-large}@media{` + `@media (max-width: 767px){.c-PJLV-iVKIeV-size-small{font-size:16px}}` + `@media (min-width: 768px){.c-PJLV-bUkcYv-size-large{font-size:24px}}` + `}` diff --git a/packages/react/tests/issue-416.js b/packages/react/tests/issue-416.js index 8a99f6d7..5c0ab3c6 100644 --- a/packages/react/tests/issue-416.js +++ b/packages/react/tests/issue-416.js @@ -53,9 +53,10 @@ describe('Issue #416: Composition versus Descendancy', () => { } let wrapper - - renderer.act(() => { - wrapper = renderer.create(React.createElement(App)) + test('it can render without errors', () => { + renderer.act(() => { + wrapper = renderer.create(React.createElement(App)) + }) }) const [boxA, boxB, genY, boxZ] = wrapper.toJSON().children diff --git a/packages/react/tests/issue-671.js b/packages/react/tests/issue-671.js new file mode 100644 index 00000000..caabac01 --- /dev/null +++ b/packages/react/tests/issue-671.js @@ -0,0 +1,30 @@ +import * as React from 'react' +import * as renderer from 'react-test-renderer' +import { createStitches } from '../src/index.js' + +describe('Issue #671', () => { + { + const { styled, getCssText } = createStitches() + + const StyledBase = styled('div', { color: 'red' }) + const Base = (props) => React.createElement(StyledBase, { ...props }) + const Bar = styled(Base, { color: 'blue' }) + + const App = () => { + return React.createElement( + 'div', + null, + // children + React.createElement(Bar, {}), + ) + } + + renderer.act(() => { + renderer.create(React.createElement(App)) + }) + + test('a stitches component extending a react component will inject the styles in the correct order', () => { + expect(getCssText()).toBe(`--sxs{--sxs:2 c-kydkiA c-gmqXFB}@media{.c-gmqXFB{color:red}.c-kydkiA{color:blue}}`) + }) + } +}) diff --git a/packages/react/tests/issue-737.js b/packages/react/tests/issue-737.js new file mode 100644 index 00000000..c54e50af --- /dev/null +++ b/packages/react/tests/issue-737.js @@ -0,0 +1,40 @@ +import * as React from 'react' +import * as renderer from 'react-test-renderer' +import { createStitches } from '../src/index.js' + +describe('Issue #737', () => { + { + const { styled, getCssText } = createStitches({ + media: { + bp1: '(min-width: 768px)', + }, + }) + + const TestComponent = styled('div', { + variants: { + variant: { + red: { + color: 'red', + }, + }, + }, + }) + + // now test that the initial variant in rendered fist + test('initial variants are rendered before responsive variants', () => { + // render responsive variants + renderer.act(() => { + renderer.create( + React.createElement(React.Fragment, null, [ + // render responsive variant first without initial + React.createElement(TestComponent, { key: 0, variant: { '@bp1': 'red' } }), + // render initial variant + React.createElement(TestComponent, { key: 1, variant: {'@initial': 'red'} }), + ]), + ) + }) + expect(getCssText()).toBe(`--sxs{--sxs:3 c-PJLV-gmqXFB-variant-red}@media{.c-PJLV-gmqXFB-variant-red{color:red}}--sxs{--sxs:4 c-PJLV-kmHUKy-variant-red}@media{@media (min-width: 768px){.c-PJLV-kmHUKy-variant-red{color:red}}}`) + + }) + } +})