|
1 |
| -import type { GriffelResetStyle, GriffelStyle } from '@griffel/core'; |
| 1 | +import type { GriffelStyle } from '@griffel/core'; |
2 | 2 | import type { UserAuthoredStyles as StyleXStyle } from '@stylexjs/stylex/lib/StyleXTypes';
|
3 | 3 |
|
4 | 4 | // Modifiable version of StyleXStyle
|
5 | 5 | type StyleXStyleBuilder = {
|
6 | 6 | -readonly [P in keyof StyleXStyle]: StyleXStyle[P];
|
7 | 7 | };
|
8 | 8 |
|
9 |
| -type ComplexValueType = { |
10 |
| - default?: string | number; |
11 |
| - [pseudoClass: `:${string}`]: string | number; |
12 |
| - [atRule: `@${string}`]: string | number; |
| 9 | +type ValueWithSelectors = { |
| 10 | + default?: string | number | ValueWithSelectors | null; |
| 11 | + [pseudoClass: `:${string}`]: string | number | ValueWithSelectors; |
| 12 | + [atRule: `@${string}`]: string | number | ValueWithSelectors; |
13 | 13 | };
|
14 | 14 |
|
15 |
| -function isComplexValueKey(key: string): key is keyof ComplexValueType { |
| 15 | +type Selector = keyof ValueWithSelectors; |
| 16 | + |
| 17 | +function isSelector(key: string): key is Selector { |
16 | 18 | return key.startsWith(':') || key.startsWith('@') || key === 'default';
|
17 | 19 | }
|
18 | 20 |
|
19 |
| -function isComplexValue(value: StyleXStyle[keyof StyleXStyle]): value is ComplexValueType { |
20 |
| - return typeof value === 'object' && value !== null; |
| 21 | +function isValueWithSelectors(value: StyleXStyle[keyof StyleXStyle]): value is ValueWithSelectors { |
| 22 | + return typeof value === 'object' && value !== null && !Array.isArray(value); |
| 23 | +} |
| 24 | + |
| 25 | +function makeValueWithSelectors( |
| 26 | + selectors: Selector[], |
| 27 | + value: string | number, |
| 28 | + existingValue?: string | number | unknown | null | undefined, |
| 29 | +): string | number | ValueWithSelectors { |
| 30 | + if (selectors.length === 0) { |
| 31 | + return value; |
| 32 | + } |
| 33 | + |
| 34 | + const selector = selectors[0]; |
| 35 | + const nestedSelectors = selectors.slice(1); |
| 36 | + |
| 37 | + if (isValueWithSelectors(existingValue)) { |
| 38 | + existingValue[selector] = makeValueWithSelectors(nestedSelectors, value, existingValue[selector]); |
| 39 | + return existingValue; |
| 40 | + } |
| 41 | + |
| 42 | + return { |
| 43 | + default: existingValue ?? null, |
| 44 | + [selector]: makeValueWithSelectors(nestedSelectors, value), |
| 45 | + } as ValueWithSelectors; |
21 | 46 | }
|
22 | 47 |
|
23 |
| -function mergeSelectorValues( |
24 |
| - selector: keyof ComplexValueType, |
25 |
| - style: GriffelStyle | GriffelResetStyle, |
26 |
| - result: StyleXStyleBuilder, |
27 |
| -) { |
28 |
| - for (const key in style) { |
29 |
| - if (!Object.prototype.hasOwnProperty.call(style, key)) { |
| 48 | +/** |
| 49 | + * Converts from Griffel's selector format to StyleX's format. |
| 50 | + * |
| 51 | + * Griffel nests the values inside of the selectors: |
| 52 | + * { |
| 53 | + * color: 'red', |
| 54 | + * ':hover': { |
| 55 | + * color: 'green', |
| 56 | + * '@media (forced-colors: active)': { |
| 57 | + * color: 'highlighttext', |
| 58 | + * }, |
| 59 | + * }, |
| 60 | + * } |
| 61 | + * |
| 62 | + * StyleX nests the selectors inside of the values: |
| 63 | + * { |
| 64 | + * color: { |
| 65 | + * default: 'red', |
| 66 | + * ':hover': { |
| 67 | + * default: 'green', |
| 68 | + * '@media (forced-colors: active)': 'highlighttext', |
| 69 | + * }, |
| 70 | + * }, |
| 71 | + * } |
| 72 | + * |
| 73 | + * @param selector The selector, such as ':hover' or '@media ...' |
| 74 | + * @param griffelStyle The griffel styles within this selector. |
| 75 | + * @param strictDomStyle The react-strict-dom style to add the merged values to. |
| 76 | + */ |
| 77 | +function mergeSelectorValues(selectors: Selector[], griffelStyle: GriffelStyle, strictDomStyle: StyleXStyleBuilder) { |
| 78 | + for (const key in griffelStyle) { |
| 79 | + if (!Object.prototype.hasOwnProperty.call(griffelStyle, key)) { |
30 | 80 | continue;
|
31 | 81 | }
|
32 | 82 |
|
33 |
| - const value = style[key as keyof GriffelStyle]; |
34 |
| - if (typeof value === 'string' || typeof value === 'number') { |
35 |
| - const property = key as keyof GriffelStyle & keyof StyleXStyle; |
| 83 | + const property = key as keyof GriffelStyle & keyof StyleXStyle; |
| 84 | + const value = griffelStyle[property]; |
36 | 85 |
|
37 |
| - // If there's already a value with another selector, add this to it |
38 |
| - const existingValue = result[property]; |
39 |
| - if (isComplexValue(existingValue)) { |
40 |
| - existingValue[selector] = value; |
41 |
| - } else { |
42 |
| - result[property] = { default: existingValue || null, [selector]: value }; |
43 |
| - } |
| 86 | + if (typeof value === 'string' || typeof value === 'number') { |
| 87 | + strictDomStyle[property] = makeValueWithSelectors(selectors, value, strictDomStyle[property]); |
| 88 | + } else if (Array.isArray(value)) { |
| 89 | + console.warn('Unsupported array value for ', key); |
| 90 | + } else if (isSelector(key) && isValueWithSelectors(value)) { |
| 91 | + mergeSelectorValues([...selectors, key], value, strictDomStyle); |
44 | 92 | } else {
|
45 |
| - throw new Error(`Unsupported nested selector: '${selector}' '${key}'`); |
| 93 | + console.warn(`Unsupported selector `, [...selectors, key]); |
46 | 94 | }
|
47 | 95 | }
|
48 | 96 | }
|
49 | 97 |
|
50 |
| -export const convertGriffelToStyleX = (style: GriffelStyle | GriffelResetStyle): StyleXStyle => { |
51 |
| - const result: StyleXStyleBuilder = {}; |
| 98 | +export const convertGriffelToStyleX = (griffelStyle: GriffelStyle): StyleXStyle => { |
| 99 | + const strictDomStyle: StyleXStyleBuilder = {}; |
52 | 100 |
|
53 |
| - for (const key in style) { |
54 |
| - if (!Object.prototype.hasOwnProperty.call(style, key)) { |
| 101 | + for (const key in griffelStyle) { |
| 102 | + if (!Object.prototype.hasOwnProperty.call(griffelStyle, key)) { |
55 | 103 | continue;
|
56 | 104 | }
|
57 | 105 |
|
58 |
| - const value = style[key as keyof GriffelStyle]; |
| 106 | + const property = key as keyof GriffelStyle & keyof StyleXStyle; |
| 107 | + |
| 108 | + const value = griffelStyle[property]; |
59 | 109 | if (value === null) {
|
60 | 110 | continue;
|
61 | 111 | }
|
62 | 112 |
|
63 | 113 | if (typeof value === 'string' || typeof value === 'number') {
|
64 |
| - const property = key as keyof GriffelStyle & keyof StyleXStyle; |
65 |
| - |
66 | 114 | // If there's already a value with :hover or :active, add a `default` case
|
67 |
| - const existingValue = result[property]; |
68 |
| - if (isComplexValue(existingValue)) { |
| 115 | + const existingValue = strictDomStyle[property]; |
| 116 | + if (isValueWithSelectors(existingValue)) { |
69 | 117 | existingValue.default = value;
|
70 | 118 | } else {
|
71 |
| - result[property] = value; |
| 119 | + strictDomStyle[property] = value; |
72 | 120 | }
|
73 | 121 | } else if (Array.isArray(value)) {
|
74 |
| - throw new Error(`Unsupported array value: '${key}'`); |
75 |
| - } else if (typeof value === 'object') { |
76 |
| - if (isComplexValueKey(key)) { |
77 |
| - mergeSelectorValues(key, value, result); |
78 |
| - } |
| 122 | + console.warn('Unsupported array value for ', key); |
| 123 | + } else if (isSelector(key) && isValueWithSelectors(value)) { |
| 124 | + mergeSelectorValues([key], value, strictDomStyle); |
79 | 125 | } else {
|
80 |
| - throw new Error(`Unsupported value: '${key}'`); |
| 126 | + console.warn(`Unsupported selector `, key); |
81 | 127 | }
|
82 | 128 | }
|
83 | 129 |
|
84 |
| - return result; |
| 130 | + return strictDomStyle; |
85 | 131 | };
|
0 commit comments