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
22 changes: 22 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -235,6 +235,28 @@ This API only allows for setting CSS variables as primitive values. For more com
> [!IMPORTANT]
> By using `VariableContext` you may need to disable the `inlineVariable` optimization

## Pseudo-elements

React Native has no pseudo-elements. A `TextInput` has one prop for each of `::selection` and `::placeholder`:

| CSS | React Native prop |
| ---------------------------------- | ---------------------- |
| `::selection { background-color }` | `selectionColor` |
| `::placeholder { color }` | `placeholderTextColor` |

`selectionColor` is the band behind the selected text, which CSS styles with `background-color`; `::selection { color }` is the selected text itself, which React Native cannot style. With Tailwind, use `selection:bg-*` on the input.

The custom properties a pseudo-element declares are its own, and `currentcolor` inside it is its own `color`, as CSS specifies. Every other declaration is dropped rather than applied to the element, and `compile()` lists what it dropped. A Metro build does not print this list.

```js
compile(
`.input::selection { background-color: red; color: white; width: 10px; }`,
).warnings();
// { values: { "::selection": ["color", "width"] } }
```

On web the stylesheet reaches the browser unchanged, so both pseudo-elements behave as CSS specifies.

## Optimizations

CSS is a dynamic styling language that use highly optimized engines that are not available in React Native. Instead, we optimize the styles to improve performance
Expand Down
53 changes: 53 additions & 0 deletions src/__tests__/compiler/pseudo-element-field-policy.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
import type { StyleRule } from "../../compiler/compiler.types";
import {
pseudoElementFieldPolicy,
scopeRuleToPseudoElement,
} from "../../compiler/pseudo-elements";

const policyFields = Object.keys(pseudoElementFieldPolicy).filter(
(key): key is keyof typeof pseudoElementFieldPolicy =>
key in pseudoElementFieldPolicy,
);

test("the policy classifies at least one field of each kind", () => {
for (const kind of ["carried", "rebuilt", "dropped"] as const) {
expect(
policyFields.filter((field) => pseudoElementFieldPolicy[field] === kind),
).not.toStrictEqual([]);
}
});

test("a carried field is kept, a rebuilt one is the pseudo-element's and a dropped one is gone", () => {
const populated: StyleRule = {
s: [1, 1],
d: [["#f00", "backgroundColor"]],
v: [["brand", "#f00"]],
c: ["c:foo"],
pe: "placeholder",
dv: 1,
a: true,
target: "style",
m: [[">=", "width", 100]],
p: { h: 1 },
cq: [{ m: [">=", "width", 100] }],
aq: [["d", "x"]],
};

const { rule: scoped } = scopeRuleToPseudoElement(populated, "selection");

expect(scoped).toBeDefined();

for (const field of policyFields) {
switch (pseudoElementFieldPolicy[field]) {
case "carried":
expect(scoped).toHaveProperty(field, populated[field]);
break;
case "dropped":
expect(scoped).not.toHaveProperty(field);
break;
case "rebuilt":
expect(scoped?.[field]).not.toStrictEqual(populated[field]);
break;
}
}
});
Loading