diff --git a/src/__tests__/vendor/tailwind/groups.tsx b/src/__tests__/vendor/tailwind/groups.tsx new file mode 100644 index 00000000..f3e2e84e --- /dev/null +++ b/src/__tests__/vendor/tailwind/groups.tsx @@ -0,0 +1,134 @@ +import { fireEvent, screen } from "@testing-library/react-native"; +import { View } from "react-native-css/components"; + +import { render } from "./_tailwind"; + +const grandparentID = "grandparentID"; +const parentID = "parent"; +const childID = "child"; + +test("Styling based on parent state (group-{modifier})", async () => { + await render( + + + , + ); + + const parent = screen.getByTestId(parentID); + const child = screen.getByTestId(childID); + + expect(parent).toHaveStyle(undefined); + expect(child).toHaveStyle(undefined); + + fireEvent(parent, "hoverIn"); + + expect(child).toHaveStyle({ color: "#fff" }); +}); + +test("Differentiating nested groups", async () => { + await render( + + + + + + , + ); + + const grandparent = screen.getByTestId(grandparentID); + const parent = screen.getByTestId(parentID); + const child = screen.getByTestId(childID); + + expect(grandparent).toHaveStyle(undefined); + expect(parent).toHaveStyle(undefined); + expect(child).toHaveStyle(undefined); + + fireEvent(grandparent, "hoverIn"); + + expect(child).toHaveStyle(undefined); + + fireEvent(parent, "hoverIn"); + + expect(child).toHaveStyle({ color: "#fff" }); +}); + +test("arbitrary groups - single className", async () => { + const { rerender } = await render( + + + , + ); + + const parent = screen.getByTestId(parentID); + const child = screen.getByTestId(childID); + + expect(parent).toHaveStyle(undefined); + expect(child).toHaveStyle(undefined); + + rerender( + + + , + ); + + expect(child).toHaveStyle({ color: "#fff" }); +}); + +test("arbitrary groups - multiple className", async () => { + const { rerender } = await render( + + + , + ); + + const parent = screen.getByTestId(parentID); + const child = screen.getByTestId(childID); + + expect(parent).toHaveStyle(undefined); + expect(child).toHaveStyle(undefined); + + rerender( + + + , + ); + + expect(parent).toHaveStyle(undefined); + expect(child).toHaveStyle(undefined); + + rerender( + + + , + ); + + expect(child).toHaveStyle({ color: "#fff" }); +}); + +test("arbitrary groups - props", async () => { + const { rerender } = await render( + + + , + ); + + const parent = screen.getByTestId(parentID); + const child = screen.getByTestId(childID); + + expect(parent).toHaveStyle(undefined); + expect(child).toHaveStyle(undefined); + + rerender( + + + , + ); + + expect(child).toHaveStyle({ color: "#fff" }); +}); diff --git a/src/compiler/__tests__/selectors.test.tsx b/src/compiler/__tests__/selectors.test.tsx index 25742b0f..48e8e66f 100644 --- a/src/compiler/__tests__/selectors.test.tsx +++ b/src/compiler/__tests__/selectors.test.tsx @@ -84,3 +84,69 @@ test(".my-class { &:is(:where(.my-parent, .my-second-parent):hover *) {} }", () }, ]); }); + +test(".group-[.test]:text-white { &:is(:where(.group):is(.test) *) {}", () => { + const result = getClassNameSelectors([ + [ + { + type: "class", + name: "group-[.test]:text-white", + }, + { + type: "nesting", + }, + { + type: "pseudo-class", + kind: "is", + selectors: [ + [ + { + type: "pseudo-class", + kind: "where", + selectors: [ + [ + { + type: "class", + name: "group", + }, + ], + ], + }, + { + type: "pseudo-class", + kind: "is", + selectors: [ + [ + { + type: "class", + name: "test", + }, + ], + ], + }, + { + type: "combinator", + value: "descendant", + }, + { + type: "universal", + }, + ], + ], + }, + ], + ]); + + expect(result).toStrictEqual([ + { + className: "group-[.test]:text-white", + containerQuery: [ + { + n: "g:group.test", + }, + ], + specificity: [0, 3], + type: "className", + }, + ]); +}); diff --git a/src/compiler/selector-builder.ts b/src/compiler/selector-builder.ts index 9172e3a9..40f021dd 100644 --- a/src/compiler/selector-builder.ts +++ b/src/compiler/selector-builder.ts @@ -437,11 +437,6 @@ function parseIsWhereComponents( } case "where": case "is": { - // :is() and :where() need to be at the start of the selector, - if (index !== 0) { - return null; - } - // Now get the selectors inside the `is` or `where` pseudo-class queries = component.selectors.flatMap((selector) => { return parseIsWhereComponents(type, selector, 0, queries) ?? []; diff --git a/src/runtime/native/react/rules.ts b/src/runtime/native/react/rules.ts index 0d5e53a4..849276dc 100644 --- a/src/runtime/native/react/rules.ts +++ b/src/runtime/native/react/rules.ts @@ -42,6 +42,7 @@ export function updateRules( const inlineVariables = new Set(); let animated = false; + let pressable = false; for (const config of state.configs) { const source = currentProps?.[config.source]; @@ -94,22 +95,19 @@ export function updateRules( } for (let rule of styleRuleSet) { - // Even if a rule does not match, make sure we register that it could set - // a variable or container or be animated. - if (rule.v) usesVariables = true; - if (rule.c) containers ??= inheritedContainers; - if (rule.a) animated = true; - usesVariables ||= Boolean(rule.dv); // We do this even if the rule doesn't match so we can maintain a consistent render tree // We we need to inject React context + if (rule.a) animated = true; + if (rule.v) { variables ??= inheritedVariables; } if (rule.c) { containers ??= inheritedContainers; + activeFamily(state.ruleEffectGetter); } if ( @@ -171,7 +169,7 @@ export function updateRules( if (process.env.NODE_ENV !== "production") { if (isRerender) { - let pressable = activeFamily.has(state.ruleEffectGetter); + const pressable = activeFamily.has(state.ruleEffectGetter); if (Boolean(variables) !== Boolean(state.variables)) { console.log( @@ -194,11 +192,7 @@ export function updateRules( } } - // We only track this in development - let pressable = - process.env.NODE_ENV === "production" - ? undefined - : activeFamily.has(state.ruleEffectGetter); + pressable = activeFamily.has(state.ruleEffectGetter); if (!rules.size && !state.stylesObs && !inlineVariables.size) { return { @@ -208,6 +202,7 @@ export function updateRules( animated, pressable, variables, + containers, }; }