import type * as CSS from 'csstype'; import type { ApplySchemaValue } from '../create-strict-api/types.js'; import { ax } from '../runtime.js'; import type { CSSPseudos, CSSProperties, StrictCSSProperties, AllCSSPseudoClasses, } from '../types.js'; type MarkAsRequired = T & { [P in K]-?: T[P] }; type XCSSValue< TStyleDecl extends keyof CSSProperties, TSchema, TPseudoKey extends AllCSSPseudoClasses | '' > = { [Q in keyof StrictCSSProperties]: Q extends TStyleDecl ? ApplySchemaValue : never; }; type XCSSPseudo< TAllowedProperties extends keyof StrictCSSProperties, TAllowedPseudos extends CSSPseudos, TRequiredProperties extends { requiredProperties: TAllowedProperties }, TSchema > = { [Q in CSSPseudos]?: Q extends TAllowedPseudos ? MarkAsRequired< XCSSValue, TRequiredProperties['requiredProperties'] > : never; }; type XCSSMediaQuery< TAllowedProperties extends keyof StrictCSSProperties, TAllowedPseudos extends CSSPseudos, TAllowedMediaQueries extends string, TSchema > = { [Q in `@media ${TAllowedMediaQueries}`]?: | XCSSValue | XCSSPseudo; }; /** * These APIs we don't want to allow to be passed through the `xcss` prop but we also * must declare them so the (lack-of a) excess property check doesn't bite us and allow * unexpected values through. */ type BlockedRules = { // To ensure styles that aren't allowed through XCSS prop strict APIs we block any // loose media queries from being passed through as we can't ensure they're correct. '@media [loose]'?: TMode extends 'loose' ? any : never; selectors?: never; } & { // We also block all type level at rule "objects" that are present on cssMap. [Q in CSS.AtRules]?: never; }; type CompiledPropertyDeclarationReference = { ['__COMPILED_PROPERTY_DECLARATION_REFERENCE_DO_NOT_WRITE_DIRECTLY__']: true; }; /** * Used to mark styles that have been flushed through an API as being generated * from Compiled. This is useful when you want other ends of the API to ensure they * take Compiled generated styles and not some arbitrary object. */ export type CompiledStyles = { [Q in keyof TObject]: TObject[Q] extends Record ? CompiledStyles : CompiledPropertyDeclarationReference & TObject[Q]; }; /** * Please think twice before using this type, you're better off declaring explicitly * what your API should be, for example only defining `"color"`. * * Use in conjunction with {@link XCSSProp} to allow all properties to be given to * your component. */ export type XCSSAllProperties = keyof StrictCSSProperties; /** * Please think twice before using this type, you're better off declaring explicitly * what your API should be, for example not allowing any pseudos at all using the * `never` type. * * Use in conjunction with {@link XCSSProp} to allow all pseudos to be given to * your component. */ export type XCSSAllPseudos = CSSPseudos; /** * ## XCSSProp * * Declare styles your component takes with all other styles marked as violations * by the TypeScript compiler. There are two primary use cases for xcss prop: * * - safe style overrides * - inverting style declarations * * Interverting style declarations is interesting for platform teams as * it means products only pay for styles they use as they're now the ones who declare * the styles! * * The {@link XCSSProp} type has generics two of which must be defined — use to explicitly * set want you to maintain as API. Use {@link XCSSAllProperties} and {@link XCSSAllPseudos} * to enable all properties and pseudos. * * The third generic is used to declare what properties and pseudos should be required. * * @example * ``` * interface MyComponentProps { * // Color is accepted, all other properties / pseudos are considered violations. * xcss?: XCSSProp<'color', never>; * * // Only backgrond color and hover pseudo is accepted. * xcss?: XCSSProp<'backgroundColor', '&:hover'>; * * // All properties are accepted, all pseudos are considered violations. * xcss?: XCSSProp; * * // All properties are accepted, only the hover pseudo is accepted. * xcss?: XCSSProp; * * // The xcss prop is required as well as the color property. No pseudos are required. * xcss: XCSSProp; * } * * function MyComponent({ xcss }: MyComponentProps) { * return
* } * ``` * * The xcss prop works with static inline objects and the [cssMap](https://compiledcssinjs.com/docs/api-cssmap) API. * * @example * ``` * // Declared as an inline object * * * // Declared with the cssMap API * const styles = cssMap({ text: { color: 'var(--ds-text)' } }); * * ``` * * To concatenate and conditonally apply styles use the {@link cssMap} {@link cx} functions. */ export type XCSSProp< TAllowedProperties extends keyof StrictCSSProperties, TAllowedPseudos extends CSSPseudos, TRequiredProperties extends { requiredProperties: TAllowedProperties; } = never > = Internal$XCSSProp< TAllowedProperties, TAllowedPseudos, string, object, TRequiredProperties, 'loose' >; export type Internal$XCSSProp< TAllowedProperties extends keyof StrictCSSProperties, TAllowedPseudos extends CSSPseudos, TAllowedMediaQueries extends string, TSchema, TRequiredProperties extends { requiredProperties: TAllowedProperties; }, TMode extends 'loose' | 'strict' > = | (MarkAsRequired< XCSSValue, TRequiredProperties['requiredProperties'] > & XCSSPseudo & XCSSMediaQuery & BlockedRules) | false | null | undefined; /** * ## CX * * Use in conjunction with the {@link XCSSProp} to concatenate and conditionally apply * declared styles. Can only be used with the `cssMap()` and {@link XCSSProp} APIs. * * @example * ``` * const styles = cssMap({ * text: { color: 'var(--ds-text)' }, * primary: { color: 'var(--ds-text-brand)' }, * }); * * * ``` */ export const cx = []]>( ...styles: TStyles ): TStyles[number] => { // At runtime TStyles is resolved down to strings, but not at compile time. // We circumvent the type system here because of that. const actualStyles = styles as unknown as string[]; // The output should be a union type of passed in styles. This ensures the call // site of xcss prop can raise violations when disallowed styles have been passed. return ax(actualStyles) as TStyles[number]; };