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,
};
}