@react-spectrum/style-macro-s1 3.0.0-alpha.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,481 @@
1
+ /*
2
+ * Copyright 2024 Adobe. All rights reserved.
3
+ * This file is licensed to you under the Apache License, Version 2.0 (the "License");
4
+ * you may not use this file except in compliance with the License. You may obtain a copy
5
+ * of the License at http://www.apache.org/licenses/LICENSE-2.0
6
+ *
7
+ * Unless required by applicable law or agreed to in writing, software distributed under
8
+ * the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR REPRESENTATIONS
9
+ * OF ANY KIND, either express or implied. See the License for the specific language
10
+ * governing permissions and limitations under the License.
11
+ */
12
+ import type {Condition, CSSProperties, CSSValue, CustomValue, PropertyFunction, PropertyValueDefinition, PropertyValueMap, StyleFunction, StyleValue, Theme, ThemeProperties, Value} from './types';
13
+
14
+ export function createArbitraryProperty<T extends Value>(fn: (value: T, property: string) => CSSProperties): PropertyFunction<T> {
15
+ return (value, property) => {
16
+ let selector = Array.isArray(value) ? value.map(v => generateArbitraryValueSelector(String(v))).join('') : generateArbitraryValueSelector(String(value));
17
+ return {default: [fn(value, property), selector]};
18
+ };
19
+ }
20
+
21
+ export function createMappedProperty<T extends CSSValue>(fn: (value: string, property: string) => CSSProperties, values: PropertyValueMap<T> | string[]): PropertyFunction<T> {
22
+ let valueMap = createValueLookup(Array.isArray(values) ? values : Object.values(values).flatMap((v: any) => typeof v === 'object' ? Object.values(v) : [v]));
23
+
24
+ return (value, property) => {
25
+ let v = parseArbitraryValue(value);
26
+ if (v) {
27
+ return {default: [fn(v[0], property), v[1]]};
28
+ }
29
+
30
+ // @ts-ignore
31
+ let val = Array.isArray(values) ? value : values[String(value)];
32
+ return mapConditionalValue(val, value => {
33
+ return [fn(value, property), valueMap.get(value)!];
34
+ });
35
+ };
36
+ }
37
+
38
+ type Color<C extends string> = C | `${string}/${number}`;
39
+ export function createColorProperty<C extends string>(colors: PropertyValueMap<C>, property?: keyof CSSProperties): PropertyFunction<Color<C>> {
40
+ let valueMap = createValueLookup(Object.values(colors).flatMap((v: any) => typeof v === 'object' ? Object.values(v) : [v]));
41
+ return (value: Color<C>, key: string) => {
42
+ let v = parseArbitraryValue(value);
43
+ if (v) {
44
+ return {default: [{[property || key]: v[0]}, v[1]]};
45
+ }
46
+
47
+ let [color, opacity] = value.split('/');
48
+ // @ts-ignore
49
+ let c = colors[color];
50
+ return mapConditionalValue(c, value => {
51
+ let css = opacity ? `rgb(from ${value} r g b / ${opacity}%)` : value;
52
+ let selector = valueMap.get(value)! + (opacity ? opacity.replace('.', '-') : '');
53
+ return [{[property || key]: css}, selector];
54
+ });
55
+ };
56
+ }
57
+
58
+ function mapConditionalValue<T, U>(value: PropertyValueDefinition<T>, fn: (value: T) => U): PropertyValueDefinition<U> {
59
+ if (typeof value === 'object') {
60
+ let res: PropertyValueDefinition<U> = {};
61
+ for (let condition in value) {
62
+ res[condition] = mapConditionalValue((value as any)[condition], fn);
63
+ }
64
+ return res;
65
+ } else {
66
+ return fn(value);
67
+ }
68
+ }
69
+
70
+ function createValueLookup(values: Array<CSSValue>, atStart = false) {
71
+ let map = new Map<CSSValue, string>();
72
+ for (let value of values) {
73
+ if (!map.has(value)) {
74
+ map.set(value, generateName(map.size, atStart));
75
+ }
76
+ }
77
+ return map;
78
+ }
79
+
80
+ function parseArbitraryValue(value: any) {
81
+ if (typeof value === 'string' && value.startsWith('--')) {
82
+ return [`var(${value})`, value];
83
+ } else if (typeof value === 'string' && value[0] === '[' && value[value.length - 1] === ']') {
84
+ let s = generateArbitraryValueSelector(value.slice(1, -1));
85
+ return [value.slice(1, -1), s];
86
+ }
87
+ }
88
+
89
+ interface MacroContext {
90
+ addAsset(asset: {type: string, content: string}): void
91
+ }
92
+
93
+ export function createTheme<T extends Theme>(theme: T): StyleFunction<ThemeProperties<T>, 'default' | Extract<keyof T['conditions'], string>> {
94
+ let themePropertyMap = createValueLookup(Object.keys(theme.properties), true);
95
+ let themeConditionMap = createValueLookup(Object.keys(theme.conditions), true);
96
+ let propertyFunctions = new Map(Object.entries(theme.properties).map(([k, v]) => {
97
+ if (typeof v === 'function') {
98
+ return [k, v];
99
+ }
100
+ return [k, createMappedProperty((value, p) => ({[p]: value}), v) as PropertyFunction<Value>];
101
+ }));
102
+
103
+ let dependencies = new Set<string>();
104
+ return function style(this: MacroContext | void, style) {
105
+ // Generate rules for each property.
106
+ let rules = new Map<string, Rule[]>();
107
+ let values = new Map();
108
+ dependencies.clear();
109
+ let usedPriorities = 1;
110
+ let setRules = (key: string, value: [number, Rule[]]) => {
111
+ usedPriorities = Math.max(usedPriorities, value[0]);
112
+ rules.set(key, value[1]);
113
+ };
114
+
115
+ for (let key in style) {
116
+ let value: any = style[key]!;
117
+ let themeProperty = key;
118
+ values.set(key, value);
119
+
120
+ // Get the type of custom properties in the theme.
121
+ if (key.startsWith('--')) {
122
+ themeProperty = value.type;
123
+ value = value.value;
124
+ }
125
+
126
+ // Expand shorthands to longhands so that merging works as expected.
127
+ if (theme.shorthands[key]) {
128
+ for (let prop of theme.shorthands[key]) {
129
+ values.set(prop, value);
130
+ setRules(prop, compileValue(prop, prop, value));
131
+ }
132
+ } else if (themeProperty in theme.properties) {
133
+ setRules(key, compileValue(key, themeProperty, value));
134
+ }
135
+ }
136
+
137
+ // For properties referenced by self(), rewrite the declarations to assign
138
+ // to an intermediary custom property so we can access the value.
139
+ for (let dep of dependencies) {
140
+ let value = values.get(dep);
141
+ if (value != null) {
142
+ if (!(dep in theme.properties)) {
143
+ throw new Error(`Unknown dependency ${dep}`);
144
+ }
145
+ let name = `--${themePropertyMap.get(dep)}`;
146
+ // Could potentially use @property to prevent the var from inheriting in children.
147
+ setRules(name, compileValue(name, dep, value));
148
+ setRules(dep, compileValue(dep, dep, name));
149
+ }
150
+ }
151
+ dependencies.clear();
152
+
153
+ // Declare layers for each priority ahead of time so the order is always correct.
154
+ let css = '@layer ';
155
+ let first = true;
156
+ for (let i = 0; i <= usedPriorities; i++) {
157
+ if (first) {
158
+ first = false;
159
+ } else {
160
+ css += ', ';
161
+ }
162
+ css += generateName(i, true);
163
+ }
164
+ css += ';\n\n';
165
+
166
+ // Generate JS and CSS for each rule.
167
+ let js = 'let rules = "";\n';
168
+ for (let propertyRules of rules.values()) {
169
+ js += printJS(propertyRules) + '\n';
170
+ for (let rule of propertyRules) {
171
+ css += printRule(rule) + '\n\n';
172
+ }
173
+ }
174
+
175
+ js += 'return rules;';
176
+
177
+ if (this && typeof this?.addAsset === 'function') {
178
+ this.addAsset({
179
+ type: 'css',
180
+ content: css
181
+ });
182
+ }
183
+
184
+ return new Function('props', js) as any;
185
+ };
186
+
187
+ function compileValue(property: string, themeProperty: string, value: StyleValue<Value, Condition<T>, any>) {
188
+ return conditionalToRules(value as any, 0, new Set(), new Set(), (value, priority, conditions, skipConditions) => {
189
+ return compileRule(property, themeProperty, value, priority, conditions, skipConditions);
190
+ });
191
+ }
192
+
193
+ function conditionalToRules<P extends CustomValue | any[]>(
194
+ value: PropertyValueDefinition<P>,
195
+ parentPriority: number,
196
+ currentConditions: Set<string>,
197
+ skipConditions: Set<string>,
198
+ fn: (value: P, priority: number, conditions: Set<string>, skipConditions: Set<string>) => [number, Rule[]]
199
+ ): [number, Rule[]] {
200
+ if (value && typeof value === 'object' && !Array.isArray(value)) {
201
+ let rules: Rule[] = [];
202
+
203
+ // Later conditions in parent rules override conditions in child rules.
204
+ let subSkipConditions = new Set([...skipConditions, ...Object.keys(value)]);
205
+
206
+ // Skip the default condition if we're already filtering by one of the other possible conditions.
207
+ // For example, if someone specifies `dark: 'gray-400'`, only include the dark version of `gray-400` from the theme.
208
+ let skipDefault = Object.keys(value).some(k => currentConditions.has(k));
209
+ let wasCSSCondition = false;
210
+ let priority = parentPriority;
211
+
212
+ for (let condition in value) {
213
+ if (skipConditions.has(condition) || (condition === 'default' && skipDefault)) {
214
+ continue;
215
+ }
216
+ subSkipConditions.delete(condition);
217
+
218
+ let val = value[condition];
219
+
220
+ // If a theme condition comes after runtime conditions, create a new grouping.
221
+ // This makes the CSS class unconditional so it appears outside the `else` block in the JS.
222
+ // The @layer order in the generated CSS will ensure that it overrides classes applied by runtime conditions.
223
+ let isCSSCondition = condition in theme.conditions || /^[@:]/.test(condition);
224
+ if (!wasCSSCondition && isCSSCondition && rules.length) {
225
+ rules = [{prelude: '', condition: '', body: rules}];
226
+ }
227
+ wasCSSCondition = isCSSCondition;
228
+
229
+ // Increment the current priority whenever we see a new CSS condition.
230
+ if (isCSSCondition) {
231
+ priority++;
232
+ }
233
+
234
+ // If this is a runtime condition, inherit the priority from the parent rule.
235
+ // Otherwise, use the current maximum of the parent and current priorities.
236
+ let rulePriority = isCSSCondition ? priority : parentPriority;
237
+
238
+ if (condition === 'default' || isCSSCondition || /^is[A-Z]/.test(condition) || /^allows[A-Z]/.test(condition)) {
239
+ let subConditions = currentConditions;
240
+ if (isCSSCondition) {
241
+ subConditions = new Set([...currentConditions, condition]);
242
+ }
243
+ let [subPriority, subRules] = conditionalToRules(val, rulePriority, subConditions, subSkipConditions, fn);
244
+ rules.push(...compileCondition(currentConditions, condition, priority, subRules));
245
+ priority = Math.max(priority, subPriority);
246
+ } else if (val && typeof val === 'object' && !Array.isArray(val)) {
247
+ for (let key in val) {
248
+ let [subPriority, subRules] = conditionalToRules(val[key], rulePriority, currentConditions, subSkipConditions, fn);
249
+ rules.push(...compileCondition(currentConditions, `${condition} === ${JSON.stringify(key)}`, priority, subRules));
250
+ priority = Math.max(priority, subPriority);
251
+ }
252
+ }
253
+ }
254
+ return [priority, rules];
255
+ } else {
256
+ return fn(value as any, parentPriority, currentConditions, skipConditions);
257
+ }
258
+ }
259
+
260
+ function compileCondition(conditions: Set<string>, condition: string, priority: number, rules: Rule[]): Rule[] {
261
+ if (condition === 'default' || conditions.has(condition)) {
262
+ return [{prelude: '', condition: '', body: rules}];
263
+ }
264
+
265
+ if (condition in theme.conditions || /^[@:]/.test(condition)) {
266
+ // Conditions starting with : are CSS pseudo classes. Nest them inside the parent rule.
267
+ let prelude = theme.conditions[condition] || condition;
268
+ if (prelude.startsWith(':')) {
269
+ return [{
270
+ prelude: `@layer ${generateName(priority, true)}`,
271
+ body: rules.map(rule => ({prelude: rule.prelude, body: [{...rule, prelude: '&' + prelude}], condition: ''})),
272
+ condition: ''
273
+ }];
274
+ }
275
+
276
+ // Otherwise, wrap the rule in the condition (e.g. @media).
277
+ return [{
278
+ // Top level layer is based on the priority of the rule, not the condition.
279
+ // Also group in a sub-layer based on the condition so that lightningcss can more effectively deduplicate rules.
280
+ prelude: `@layer ${generateName(priority, true)}.${themeConditionMap.get(condition) || generateArbitraryValueSelector(condition, true)}`,
281
+ body: [{prelude, body: rules, condition: ''}],
282
+ condition: ''
283
+ }];
284
+ }
285
+
286
+ return [{prelude: '', condition, body: rules}];
287
+ }
288
+
289
+ function compileRule(property: string, themeProperty: string, value: Value, priority: number, conditions: Set<string>, skipConditions: Set<string>): [number, Rule[]] {
290
+ // Generate selector. This consists of three parts:
291
+ // 1. Property. For custom properties we use a hash. For theme properties, we use the index within the theme.
292
+ // 2. Conditions. This uses the index within the theme.
293
+ // 3. Value. The index in the theme, or a hash for arbitrary values.
294
+ let prelude = '.';
295
+ if (property.startsWith('--')) {
296
+ prelude += generateArbitraryValueSelector(property, true) + '-';
297
+ } else {
298
+ prelude += themePropertyMap.get(themeProperty);
299
+ }
300
+
301
+ let propertyFunction = propertyFunctions.get(themeProperty);
302
+ if (propertyFunction) {
303
+ // Expand value to conditional CSS values, and then to rules.
304
+ let res = propertyFunction(value, property);
305
+ return conditionalToRules(res, priority, conditions, skipConditions, (value, priority, conditions) => {
306
+ let [obj, p] = value;
307
+ let body = '';
308
+ for (let key in obj) {
309
+ let k = key as any;
310
+ let value = obj[k];
311
+ if (value === undefined) {
312
+ continue;
313
+ }
314
+ if (typeof value === 'string') {
315
+ // Replace self() references with variables and track the dependencies.
316
+ value = value.replace(/self\(([a-zA-Z]+)/g, (_, v) => {
317
+ dependencies.add(v);
318
+ return `var(--${themePropertyMap.get(v)}`;
319
+ });
320
+ }
321
+ body += `${kebab(key)}: ${value};`;
322
+ }
323
+
324
+ let selector = prelude;
325
+ if (conditions.size > 0) {
326
+ for (let condition of conditions) {
327
+ selector += themeConditionMap.get(condition) || generateArbitraryValueSelector(condition);
328
+ }
329
+ }
330
+
331
+ let rules: Rule[] = [{
332
+ condition: '',
333
+ prelude: selector + p,
334
+ body
335
+ }];
336
+
337
+ if (conditions.size === 0) {
338
+ rules = [{
339
+ prelude: '@layer a',
340
+ body: rules,
341
+ condition: ''
342
+ }];
343
+ }
344
+
345
+ return [0, rules];
346
+ });
347
+ } else {
348
+ throw new Error('Unknown property ' + themeProperty);
349
+ }
350
+ }
351
+ }
352
+
353
+ function kebab(property: string) {
354
+ if (property.startsWith('--')) {
355
+ return property;
356
+ }
357
+ return property.replace(/([a-z])([A-Z])/g, (_, a, b) => `${a}-${b.toLowerCase()}`);
358
+ }
359
+
360
+ interface Rule {
361
+ prelude: string,
362
+ condition: string,
363
+ body: string | Rule[]
364
+ }
365
+
366
+ // Generate a class name from a number, e.g. index within the theme.
367
+ // This maps to an alphabet containing lower case letters, upper case letters, and numbers.
368
+ // For numbers larger than 62, an underscore is prepended.
369
+ // This encoding allows easy parsing to enable runtime merging by property.
370
+ function generateName(index: number, atStart = false): string {
371
+ if (index < 26) {
372
+ // lower case letters
373
+ return String.fromCharCode(index + 97);
374
+ }
375
+
376
+ if (index < 52) {
377
+ // upper case letters
378
+ return String.fromCharCode((index - 26) + 65);
379
+ }
380
+
381
+ if (index < 62) {
382
+ // numbers
383
+ let res = String.fromCharCode((index - 52) + 48);
384
+ if (atStart) {
385
+ res = '_' + res;
386
+ }
387
+ return res;
388
+ }
389
+
390
+ return '_' + generateName(index - 62);
391
+ }
392
+
393
+ // For arbitrary values, we use a hash of the string to generate the class name.
394
+ function generateArbitraryValueSelector(v: string, atStart = false) {
395
+ let c = hash(v).toString(36);
396
+ if (atStart && /^[0-9]/.test(c)) {
397
+ c = `_${c}`;
398
+ }
399
+ return `-${c}`;
400
+ }
401
+
402
+ // djb2 hash function.
403
+ // http://www.cse.yorku.ca/~oz/hash.html
404
+ function hash(v: string) {
405
+ let hash = 5381;
406
+ for (let i = 0; i < v.length; i++) {
407
+ hash = ((hash << 5) + hash) + v.charCodeAt(i) >>> 0;
408
+ }
409
+ return hash;
410
+ }
411
+
412
+ function printRule(rule: Rule, indent = ''): string {
413
+ if (!rule.prelude && typeof rule.body !== 'string') {
414
+ return rule.body.map(b => printRule(b, indent)).join('\n\n');
415
+ }
416
+
417
+ return `${indent}${rule.prelude} {
418
+ ${typeof rule.body === 'string' ? indent + ' ' + rule.body : rule.body.map(b => printRule(b, indent + ' ')).join('\n\n')}
419
+ ${indent}}`;
420
+ }
421
+
422
+ function printJS(rules: Rule[], indent = ''): string {
423
+ rules = rules.slice().reverse();
424
+
425
+ let conditional = rules.filter(rule => rule.condition).map((rule, i) => {
426
+ return `${i > 0 ? ' else ' : ''}if (props.${rule.condition}) {\n${indent} ${printRuleChildren(rule, indent + ' ')}\n${indent}}`;
427
+ });
428
+
429
+ let elseCases = rules.filter(rule => !rule.condition).map(rule => printRuleChildren(rule, indent + ' '));
430
+
431
+ if (conditional.length && elseCases.length) {
432
+ return `${conditional.join('')} else {\n${indent} ${elseCases.join('\n' + indent + ' ')}\n${indent}}`;
433
+ }
434
+
435
+ if (conditional.length) {
436
+ return conditional.join('');
437
+ }
438
+
439
+ return elseCases.join('\n' + indent);
440
+ }
441
+
442
+ function printRuleChildren(rule: Rule, indent = '') {
443
+ let res = '';
444
+ if (rule.prelude.startsWith('.')) {
445
+ res += `rules += ' ${rule.prelude.slice(1)}';`;
446
+ }
447
+
448
+ if (Array.isArray(rule.body)) {
449
+ res += printJS(rule.body, indent);
450
+ }
451
+
452
+ return res;
453
+ }
454
+
455
+ export function raw(this: MacroContext | void, css: string) {
456
+ let className = generateArbitraryValueSelector(css, true);
457
+ css = `.${className} {
458
+ ${css}
459
+ }`;
460
+ if (this && typeof this?.addAsset === 'function') {
461
+ this.addAsset({
462
+ type: 'css',
463
+ content: css
464
+ });
465
+ }
466
+ return className;
467
+ }
468
+
469
+ export function keyframes(this: MacroContext | void, css: string) {
470
+ let name = generateArbitraryValueSelector(css, true);
471
+ css = `@keyframes ${name} {
472
+ ${css}
473
+ }`;
474
+ if (this && typeof this?.addAsset === 'function') {
475
+ this.addAsset({
476
+ type: 'css',
477
+ content: css
478
+ });
479
+ }
480
+ return name;
481
+ }
package/src/types.ts ADDED
@@ -0,0 +1,172 @@
1
+ /*
2
+ * Copyright 2024 Adobe. All rights reserved.
3
+ * This file is licensed to you under the Apache License, Version 2.0 (the "License");
4
+ * you may not use this file except in compliance with the License. You may obtain a copy
5
+ * of the License at http://www.apache.org/licenses/LICENSE-2.0
6
+ *
7
+ * Unless required by applicable law or agreed to in writing, software distributed under
8
+ * the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR REPRESENTATIONS
9
+ * OF ANY KIND, either express or implied. See the License for the specific language
10
+ * governing permissions and limitations under the License.
11
+ */
12
+ import type * as CSS from 'csstype';
13
+
14
+ export type CSSValue = string | number;
15
+ export type CustomValue = string | number | boolean;
16
+ export type Value = CustomValue | readonly CustomValue[];
17
+ export type PropertyValueDefinition<T> = T | {[condition: string]: PropertyValueDefinition<T>};
18
+ export type PropertyValueMap<T extends CSSValue = CSSValue> = {
19
+ [name in T]: PropertyValueDefinition<string>
20
+ };
21
+
22
+ export type CustomProperty = `--${string}`;
23
+ export type CSSProperties = CSS.Properties & {
24
+ [k: CustomProperty]: CSSValue
25
+ };
26
+
27
+ export type PropertyFunction<T extends Value> = (value: T, property: string) => PropertyValueDefinition<[CSSProperties, string]>;
28
+
29
+ export interface Theme {
30
+ properties: {
31
+ [name: string]: PropertyValueMap | PropertyFunction<any> | string[]
32
+ },
33
+ conditions: {
34
+ [name: string]: string
35
+ },
36
+ shorthands: {
37
+ [name: string]: string[]
38
+ }
39
+ }
40
+
41
+ type PropertyValue<T> =
42
+ T extends PropertyFunction<infer P>
43
+ ? P
44
+ : T extends PropertyValueMap<infer P>
45
+ ? P
46
+ : T extends string[]
47
+ ? T[number]
48
+ : never;
49
+
50
+ type PropertyValue2<T> = PropertyValue<T> | CustomProperty | `[${string}]`;
51
+ type Merge<T> = T extends any ? T : never;
52
+
53
+ // Pre-compute value types for all theme properties ahead of time.
54
+ export type ThemeProperties<T extends Theme> = Merge<{
55
+ [K in keyof T['properties'] | keyof T['shorthands']]: K extends keyof T['properties']
56
+ ? Merge<PropertyValue2<T['properties'][K]>>
57
+ : Merge<PropertyValue2<T['properties'][T['shorthands'][K][0]]>>
58
+ }>;
59
+
60
+ type Style<T extends ThemeProperties<Theme>, C extends string, R extends RenderProps<string>> =
61
+ StaticProperties<T, C, R> & CustomProperties<T, C, R>;
62
+
63
+ type StaticProperties<T extends ThemeProperties<Theme>, C extends string, R extends RenderProps<string>> = {
64
+ [Name in keyof T]?: StyleValue<T[Name], C, R>
65
+ };
66
+
67
+ type CustomProperties<T extends ThemeProperties<Theme>, C extends string, R extends RenderProps<string>> = {
68
+ [key: CustomProperty]: CustomPropertyValue<T, keyof T, C, R>
69
+ };
70
+
71
+ // Infer the value type of custom property values from the `type` key, which references a theme property.
72
+ type CustomPropertyValue<T extends ThemeProperties<Theme>, P extends keyof T, C extends string, R extends RenderProps<string>> =
73
+ P extends any
74
+ ? {type: P, value: StyleValue<T[P], C, R>}
75
+ : never;
76
+
77
+ export type RenderProps<K extends string> = {
78
+ [key in K]: any
79
+ };
80
+
81
+ export type StyleValue<V extends Value, C extends string, R extends RenderProps<string>> = V | Conditional<V, C, R>;
82
+ export type Condition<T extends Theme> = 'default' | Extract<keyof T['conditions'], string>;
83
+ type Conditional<V extends Value, C extends string, R extends RenderProps<string>> =
84
+ CSSConditions<V, C, R> & RuntimeConditions<V, C, R>
85
+
86
+ type ArbitraryCondition = `:${string}` | `@${string}`;
87
+ type CSSConditions<V extends Value, C extends string, R extends RenderProps<string>> = {
88
+ [name in C]?: StyleValue<V, C, R>
89
+ };
90
+
91
+ // If render props are unknown, allow any custom conditions to be inferred.
92
+ // Unfortunately this breaks "may only specify known properties" errors.
93
+ type RuntimeConditions<V extends Value, C extends string, R extends RenderProps<string>> =
94
+ [R] extends [never]
95
+ ? UnknownConditions<V, C>
96
+ : RenderPropConditions<V, C, R>;
97
+
98
+ type UnknownConditions<V extends Value, C extends string> = {
99
+ [name: string]: StyleValue<V, C, never> | VariantMap<string, V, C, never>
100
+ };
101
+
102
+ type BooleanConditionName = `is${Capitalize<string>}` | `allows${Capitalize<string>}`;
103
+ type RenderPropConditions<V extends Value, C extends string, R extends RenderProps<string>> = {
104
+ [K in keyof R]?: K extends BooleanConditionName ? StyleValue<V, C, R> : VariantMap<R[K], V, C, R>
105
+ };
106
+
107
+ type Values<T, K extends keyof T = keyof T> = {
108
+ [k in K]: T[k]
109
+ }[K];
110
+
111
+ export type VariantMap<K extends CSSValue, V extends Value, C extends string, R extends RenderProps<string>> = {
112
+ [k in K]?: StyleValue<V, C, R>
113
+ };
114
+
115
+ // These types are used to recursively extract all runtime conditions/variants in case an
116
+ // explicit render prop generic type is not provided/inferred. This allows the returned function
117
+ // to automatically accept the correct arguments based on the style definition.
118
+ type ExtractConditionalValue<C extends keyof any, V> = V extends Value
119
+ ? never
120
+ // Add the keys from this level for boolean conditions not in the theme.
121
+ : RuntimeConditionObject<Extract<keyof V, BooleanConditionName>, boolean>
122
+ // Add variant values for non-boolean named keys.
123
+ | Variants<V, Exclude<keyof V, C | BooleanConditionName>>
124
+ // Recursively include conditions from the next level.
125
+ | ExtractConditionalValue<C,
126
+ | Values<V, Extract<keyof V, C | BooleanConditionName>>
127
+ // And skip over variants to get to the values.
128
+ | Values<Values<V, Exclude<keyof V, C | BooleanConditionName>>>
129
+ >;
130
+
131
+ type RuntimeConditionObject<K, V> = K extends keyof any ? { [P in K]?: V } : never;
132
+
133
+ type Variants<T, K extends keyof T> = K extends any ? {
134
+ [k in K]?: keyof T[k]
135
+ } : never;
136
+
137
+ type InferCustomPropertyValue<T> = T extends {value: infer V} ? V : never;
138
+
139
+ // https://stackoverflow.com/questions/49401866/all-possible-keys-of-an-union-type
140
+ type KeysOfUnion<T> = T extends T ? keyof T: never;
141
+ type KeyValue<T, K extends KeysOfUnion<T>> = T extends {[k in K]?: any} ? T[K] : never;
142
+ type MergeUnion<T> = { [K in KeysOfUnion<T>]: KeyValue<T, K> };
143
+
144
+ type RuntimeConditionsObject<C extends keyof any, S extends Style<any, any, any>> = MergeUnion<
145
+ ExtractConditionalValue<C,
146
+ | Values<S, Exclude<keyof S, CustomProperty>>
147
+ // Skip top-level object for custom properties and go straight to value.
148
+ | InferCustomPropertyValue<Values<S, Extract<keyof S, CustomProperty>>>
149
+ >
150
+ >;
151
+
152
+ // Return an intersection between string and the used style props so we can prevent passing certain properties to components.
153
+ type IncludedProperties<S> = Merge<{
154
+ [K in keyof S]: unknown
155
+ }>;
156
+ type Keys<R> = [R] extends [never] ? never : keyof R;
157
+ export type RuntimeStyleFunction<S, R> = Keys<R> extends never ? () => string & S : (props: R) => string & S;
158
+
159
+ // If an render prop type was provided, use that so that we get autocomplete for conditions.
160
+ // Otherwise, fall back to inferring the render props from the style definition itself.
161
+ type InferProps<R, C extends keyof any, S extends Style<any, any, any>> = [R] extends [never] ? AllowOthers<RuntimeConditionsObject<C, S>> : R;
162
+ type AllowOthers<R> = Keys<R> extends never ? never : R | {[x: string]: any}
163
+ export type StyleFunction<T extends ThemeProperties<Theme>, C extends string> =
164
+ <R extends RenderProps<string> = never, S extends Style<T, C, R> = Style<T, C | ArbitraryCondition, R>>(style: S) => RuntimeStyleFunction<IncludedProperties<S>, InferProps<R, C | ArbitraryCondition, S>>;
165
+
166
+ // Creates a version of ThemeProperties with excluded keys mapped to never.
167
+ // This allows creating a component prop that only accepts certain style props.
168
+ type LimitTheme<T, P> = Merge<{
169
+ [K in keyof T]?: K extends P ? unknown : never
170
+ }>;
171
+
172
+ export type CSSProp<S, P extends string | number> = S extends StyleFunction<infer T, any> ? string & LimitTheme<T, P> : never;