# @union-web/react-plugin The React plugin provides runtime style resolution for Union themes. It adapts Union's C++ core concepts (elements, selectors, queries) into React hooks and context, enabling components to resolve their styles on the fly based on role, state, and position in the element tree. > **Note:** This project uses a ratcheting development style. The SolidJS plugin is the > currently active runtime. The react-plugin may lag behind in architecture: > > - **Ancestor state propagation** — Solid's `AncestorElementData` includes `states`; > the react-plugin omits it, breaking selectors like `toolbar:disabled > button`. > - **Typed style props** — Solid uses `UnionStyleProps` + `StyledRect` (preserves Union's > semantic model); the react-plugin uses flattened `styleGroupToCss` → raw CSS objects. > - **`unionIR`/`unionLint`** — These Vite plugins have been extracted to > `@union-web/vite-plugin` and re-exported from here. They are shared, not duplicated. ## How It Adapts Union's API The web has no built-in element tree. The plugin recovers hierarchy through **explicit ancestor context**: | Union Concept | React Plugin Equivalent | |-------------|------------------------| | `Union::Element` | `createElement(role, opts)` inside `useUnion()` | | `Union::ElementQuery` | `resolveElement()` from core | | Widget tree (implicit in Qt) | `UnionProvider` chain via React context | | `Union::StyleRule` collection | `StyleRule[]` from `UnionIRContext` | Components call `useUnion()` and wrap children in the returned `UnionProvider` to extend the ancestor chain. This enables selectors like `textarea > .placeholder` to match because `Placeholder` can see `textarea` in its ancestor context. Any third-party component can opt in to Union theming by calling `useUnion()` with the appropriate role. ## API Reference ### UseUnionOptions ```ts interface UseUnionOptions { role: string; context?: string[] | null; state?: string[]; hints?: string[]; attributes?: Record; ids?: string[]; debug?: boolean; } ``` | Option | Description | |--------|-------------| | `role` | Element type for matching (e.g., `'button'`, `'textarea'`). Required. | | `context` | Explicit ancestor roles. If `null`, becomes a root element (no ancestors). If unset, inherits from nearest `UnionProvider`. | | `state` | Active states (e.g., `['hovered']`, `['visual-focus']`). | | `hints` | Semantic hints for matching (parallels CSS `.hint` selectors). | | `attributes` | Key-value attribute pairs for attribute selectors. | | `ids` | Element IDs for ID selectors. | | `debug` | Enable console group logging of selector matching results. | ### UseUnionResult ```ts interface UseUnionResult { styles: Record; current: StylePropertyGroup; UnionProvider: ({ children }: { children: React.ReactNode }) => React.ReactElement; } ``` | Return | Description | |--------|-------------| | `styles` | Pre-computed `StylePropertyGroup` for all standard state combinations (base, hovered, pressed, checked, visual-focus, active-focus, disabled, highlighted, plus checked combos). | | `current` | The `StylePropertyGroup` for the element's current state. | | `UnionProvider` | A component wrapping `UnionAncestorContext.Provider`. Pushes this element into the ancestor chain for descendant `useUnion()` calls. | ### useUnion() ```ts function useUnion(options: UseUnionOptions): UseUnionResult ``` The main hook. It: 1. Reads `StyleRule[]` from `UnionIRContext` (set by `UnionIRProvider` at the app root). 2. Reads ancestor chain from `UnionAncestorContext` (set by parent `UnionProvider`s). 3. Pre-computes all standard state combinations via `resolveElement()`. 4. Returns `{ styles, current, UnionProvider }`. If a `UnionProvider` is not rendered (leaf component), the hook still resolves styles correctly — it just doesn't extend the ancestor chain for any deeper components. ### UnionIRProvider ```ts function UnionIRProvider({ rules, children }: { rules: StyleRule[]; children: React.ReactNode }): React.ReactElement ``` Root provider. Supplies the parsed CSS rules to all `useUnion` calls in the tree. Must be placed at the top of the application. ### UnionIRContext / UnionAncestorContext ```ts UnionIRContext: React.Context<{ rules: StyleRule[] }> // style rules from theme UnionAncestorContext: React.Context<{ elements: AncestorElementData[] }> // ancestor chain ``` ### unionIR (Vite Plugin) Now lives in `@union-web/vite-plugin`, re-exported from `@union-web/react-plugin/vite`. ```ts import { unionIR } from '@union-web/react-plugin/vite'; // or directly: import { unionIR } from '@union-web/vite-plugin/vite'; ``` Build-time plugin that: 1. Loads and parses defaults CSS, theme CSS, and `.colors` file. 2. Resolves all variable expressions (`var()`, `custom-color()`, `modify-color()`, `mix()`) using `KColorSchemeResolver` and `VariableResolver`. 3. Provides a `virtual:union-ir` module exporting the resolved `StyleRule[]` and CSS variable map. 4. Injects resolved CSS variables into HTML ``. 5. Watches input files for HMR. ```ts interface UnionIROptions { theme: string; // path to theme style.css colors: string; // path to .colors file defaults: string; // path to defaults CSS } ``` ## Using `UnionProvider` Render a `UnionProvider` for any component that has descendants that will call `useUnion()`: ```tsx function TextArea(props) { const { current, UnionProvider } = useUnion({ role: 'textarea', state: [...] }); return (