UNPKG

@compiled/react

Version:

A familiar and performant compile time CSS-in-JS library for React.

114 lines (105 loc) 3.97 kB
import React, { type ReactElement } from 'react'; import { isServerEnvironment } from './is-server-environment.js'; export interface StyleContainerConfig { /** * The DOM node into which Compiled will insert `<style>` elements. */ container: HTMLElement | ShadowRoot; /** * A unique key used to namespace the deduplication cache for this container. * Styles inserted into this container are tracked separately from the main * document cache, preventing cross-container cache collisions. * * Choose a key that is unique per container instance (e.g. `"shadow-toolbar"`). */ cacheKey: string; } /** * Singleton holding the currently active style container config on the client. * Read directly by useStyleContainer(). */ export let clientStyleContainer: StyleContainerConfig | null = null; /** * Returns the currently active style container config. */ export const useStyleContainer = (): StyleContainerConfig | null => { return clientStyleContainer; }; /** * Provides a custom DOM container for Compiled style injection within a React subtree. * * **Runtime mode only.** This provider is not supported in server environments or when * using CSS extraction (`@compiled/babel-plugin-strip-runtime`). In extraction mode, * `CS`/`CC` components are removed at build time, so there is nothing for this provider * to intercept. Supporting Shadow DOM with extraction is a known limitation and is * planned as future work. * * Use this when rendering Compiled components inside a Shadow DOM, where styles * inserted into the main document `<head>` are not visible to the shadow tree. * * The `cacheKey` must be unique per container and is used to namespace the * deduplication cache so styles are tracked independently per container. * * @example * ```tsx * import { StyleContainerProvider } from '@compiled/react'; * import { createPortal } from 'react-dom'; * * function ShadowHost() { * const hostRef = useRef<HTMLDivElement>(null); * const [shadowRoot, setShadowRoot] = useState<ShadowRoot | null>(null); * * useEffect(() => { * if (hostRef.current && !hostRef.current.shadowRoot) { * setShadowRoot(hostRef.current.attachShadow({ mode: 'open' })); * } * }, []); * * return ( * <div ref={hostRef}> * {shadowRoot && * createPortal( * <StyleContainerProvider container={shadowRoot} cacheKey="my-shadow-root"> * <MyCompiledComponent /> * </StyleContainerProvider>, * shadowRoot * )} * </div> * ); * } * ``` */ export function StyleContainerProvider({ container, cacheKey, children, }: StyleContainerConfig & { children: React.ReactNode }): ReactElement { if (isServerEnvironment()) { if (process.env.NODE_ENV === 'development') { console.warn( '@compiled/react: StyleContainerProvider has no effect in server environments. ' + 'Shadow DOM content should be rendered client-side only (e.g. inside a portal ' + 'guarded by useEffect/useState).' ); } return children as ReactElement; } // On the client, set the singleton synchronously during the render phase via // useMemo so CS children pick it up immediately. // eslint-disable-next-line react-hooks/rules-of-hooks React.useMemo(() => { clientStyleContainer = { container, cacheKey }; }, [container, cacheKey]); // Clear the singleton when this provider unmounts. // Note: nested StyleContainerProviders are not supported. If an inner provider // unmounts, this will clear clientStyleContainer to null rather than restoring // the outer provider's value. This is an acceptable limitation for the intended // use case of a single provider wrapping a shadow DOM subtree. // eslint-disable-next-line react-hooks/rules-of-hooks React.useEffect(() => { return () => { clientStyleContainer = null; }; }, []); return children as ReactElement; }