/* * Copyright 2024 Adobe. All rights reserved. * This file is licensed to you under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. You may obtain a copy * of the License at http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software distributed under * the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR REPRESENTATIONS * OF ANY KIND, either express or implied. See the License for the specific language * governing permissions and limitations under the License. */ import type * as CSS from 'csstype'; export type CSSValue = string | number; export type CustomValue = string | number | boolean; export type Value = CustomValue | readonly CustomValue[]; export type PropertyValueDefinition = T | {[condition: string]: PropertyValueDefinition}; export type PropertyValueMap = { [name in T]: PropertyValueDefinition }; export type CustomProperty = `--${string}`; export type CSSProperties = CSS.Properties & { [k: CustomProperty]: CSSValue }; export type PropertyFunction = (value: T, property: string) => PropertyValueDefinition<[CSSProperties, string]>; export interface Theme { properties: { [name: string]: PropertyValueMap | PropertyFunction | string[] }, conditions: { [name: string]: string }, shorthands: { [name: string]: string[] } } type PropertyValue = T extends PropertyFunction ? P : T extends PropertyValueMap ? P : T extends string[] ? T[number] : never; type PropertyValue2 = PropertyValue | CustomProperty | `[${string}]`; type Merge = T extends any ? T : never; // Pre-compute value types for all theme properties ahead of time. export type ThemeProperties = Merge<{ [K in keyof T['properties'] | keyof T['shorthands']]: K extends keyof T['properties'] ? Merge> : Merge> }>; type Style, C extends string, R extends RenderProps> = StaticProperties & CustomProperties; type StaticProperties, C extends string, R extends RenderProps> = { [Name in keyof T]?: StyleValue }; type CustomProperties, C extends string, R extends RenderProps> = { [key: CustomProperty]: CustomPropertyValue }; // Infer the value type of custom property values from the `type` key, which references a theme property. type CustomPropertyValue, P extends keyof T, C extends string, R extends RenderProps> = P extends any ? {type: P, value: StyleValue} : never; export type RenderProps = { [key in K]: any }; export type StyleValue> = V | Conditional; export type Condition = 'default' | Extract; type Conditional> = CSSConditions & RuntimeConditions type ArbitraryCondition = `:${string}` | `@${string}`; type CSSConditions> = { [name in C]?: StyleValue }; // If render props are unknown, allow any custom conditions to be inferred. // Unfortunately this breaks "may only specify known properties" errors. type RuntimeConditions> = [R] extends [never] ? UnknownConditions : RenderPropConditions; type UnknownConditions = { [name: string]: StyleValue | VariantMap }; type BooleanConditionName = `is${Capitalize}` | `allows${Capitalize}`; type RenderPropConditions> = { [K in keyof R]?: K extends BooleanConditionName ? StyleValue : VariantMap }; type Values = { [k in K]: T[k] }[K]; export type VariantMap> = { [k in K]?: StyleValue }; // These types are used to recursively extract all runtime conditions/variants in case an // explicit render prop generic type is not provided/inferred. This allows the returned function // to automatically accept the correct arguments based on the style definition. type ExtractConditionalValue = V extends Value ? never // Add the keys from this level for boolean conditions not in the theme. : RuntimeConditionObject, boolean> // Add variant values for non-boolean named keys. | Variants> // Recursively include conditions from the next level. | ExtractConditionalValue> // And skip over variants to get to the values. | Values>> >; type RuntimeConditionObject = K extends keyof any ? { [P in K]?: V } : never; type Variants = K extends any ? { [k in K]?: keyof T[k] } : never; type InferCustomPropertyValue = T extends {value: infer V} ? V : never; // https://stackoverflow.com/questions/49401866/all-possible-keys-of-an-union-type type KeysOfUnion = T extends T ? keyof T: never; type KeyValue> = T extends {[k in K]?: any} ? T[K] : never; type MergeUnion = { [K in KeysOfUnion]: KeyValue }; type RuntimeConditionsObject> = MergeUnion< ExtractConditionalValue> // Skip top-level object for custom properties and go straight to value. | InferCustomPropertyValue>> > >; // Return an intersection between string and the used style props so we can prevent passing certain properties to components. type IncludedProperties = Merge<{ [K in keyof S]: unknown }>; type Keys = [R] extends [never] ? never : keyof R; export type RuntimeStyleFunction = Keys extends never ? () => string & S : (props: R) => string & S; // If an render prop type was provided, use that so that we get autocomplete for conditions. // Otherwise, fall back to inferring the render props from the style definition itself. type InferProps> = [R] extends [never] ? AllowOthers> : R; type AllowOthers = Keys extends never ? never : R | {[x: string]: any} export type StyleFunction, C extends string> = = never, S extends Style = Style>(style: S) => RuntimeStyleFunction, InferProps>; // Creates a version of ThemeProperties with excluded keys mapped to never. // This allows creating a component prop that only accepts certain style props. type LimitTheme = Merge<{ [K in keyof T]?: K extends P ? unknown : never }>; export type CSSProp = S extends StyleFunction ? string & LimitTheme : never;