@compiled/react 0.16.0 → 0.16.2
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.
- package/dist/browser/css-map/index.d.ts +15 -26
- package/dist/browser/css-map/index.js +5 -4
- package/dist/browser/css-map/index.js.map +1 -1
- package/dist/browser/index.d.ts +1 -0
- package/dist/browser/index.js +1 -0
- package/dist/browser/index.js.map +1 -1
- package/dist/browser/jsx/jsx-local-namespace.d.ts +5 -1
- package/dist/browser/runtime/ac.d.ts +2 -2
- package/dist/browser/runtime/ac.js.map +1 -1
- package/dist/browser/runtime/ax.d.ts +1 -1
- package/dist/browser/runtime/ax.js.map +1 -1
- package/dist/browser/types.d.ts +9 -1
- package/dist/browser/xcss-prop/index.d.ts +130 -0
- package/dist/browser/xcss-prop/index.js +26 -0
- package/dist/browser/xcss-prop/index.js.map +1 -0
- package/dist/cjs/css-map/index.d.ts +15 -26
- package/dist/cjs/css-map/index.js +5 -4
- package/dist/cjs/css-map/index.js.map +1 -1
- package/dist/cjs/index.d.ts +1 -0
- package/dist/cjs/index.js +3 -1
- package/dist/cjs/index.js.map +1 -1
- package/dist/cjs/jsx/jsx-local-namespace.d.ts +5 -1
- package/dist/cjs/runtime/ac.d.ts +2 -2
- package/dist/cjs/runtime/ac.js.map +1 -1
- package/dist/cjs/runtime/ax.d.ts +1 -1
- package/dist/cjs/runtime/ax.js.map +1 -1
- package/dist/cjs/types.d.ts +9 -1
- package/dist/cjs/xcss-prop/index.d.ts +130 -0
- package/dist/cjs/xcss-prop/index.js +30 -0
- package/dist/cjs/xcss-prop/index.js.map +1 -0
- package/dist/esm/css-map/index.d.ts +15 -26
- package/dist/esm/css-map/index.js +5 -4
- package/dist/esm/css-map/index.js.map +1 -1
- package/dist/esm/index.d.ts +1 -0
- package/dist/esm/index.js +1 -0
- package/dist/esm/index.js.map +1 -1
- package/dist/esm/jsx/jsx-local-namespace.d.ts +5 -1
- package/dist/esm/runtime/ac.d.ts +2 -2
- package/dist/esm/runtime/ac.js.map +1 -1
- package/dist/esm/runtime/ax.d.ts +1 -1
- package/dist/esm/runtime/ax.js.map +1 -1
- package/dist/esm/types.d.ts +9 -1
- package/dist/esm/xcss-prop/index.d.ts +130 -0
- package/dist/esm/xcss-prop/index.js +26 -0
- package/dist/esm/xcss-prop/index.js.map +1 -0
- package/package.json +2 -2
- package/src/css/__tests__/types.test.ts +1 -0
- package/src/css-map/index.ts +19 -33
- package/src/index.ts +1 -0
- package/src/jsx/jsx-local-namespace.ts +8 -1
- package/src/runtime/ac.ts +1 -1
- package/src/runtime/ax.ts +1 -1
- package/src/types.ts +68 -0
- package/src/xcss-prop/__tests__/xcss-prop.test.tsx +294 -0
- package/src/xcss-prop/index.ts +174 -0
- package/dist/browser/css-map/pseudos.d.ts +0 -1
- package/dist/browser/css-map/pseudos.js +0 -5
- package/dist/browser/css-map/pseudos.js.map +0 -1
- package/dist/cjs/css-map/pseudos.d.ts +0 -1
- package/dist/cjs/css-map/pseudos.js +0 -6
- package/dist/cjs/css-map/pseudos.js.map +0 -1
- package/dist/esm/css-map/pseudos.d.ts +0 -1
- package/dist/esm/css-map/pseudos.js +0 -5
- package/dist/esm/css-map/pseudos.js.map +0 -1
- package/src/css-map/pseudos.ts +0 -59
|
@@ -68,7 +68,14 @@ export namespace CompiledJSX {
|
|
|
68
68
|
export type IntrinsicAttributes = ReactJSXIntrinsicAttributes;
|
|
69
69
|
export type IntrinsicClassAttributes<T> = ReactJSXIntrinsicClassAttributes<T>;
|
|
70
70
|
export type IntrinsicElements = {
|
|
71
|
-
[K in keyof ReactJSXIntrinsicElements]: ReactJSXIntrinsicElements[K] & {
|
|
71
|
+
[K in keyof ReactJSXIntrinsicElements]: Omit<ReactJSXIntrinsicElements[K], 'className'> & {
|
|
72
|
+
// We override class name so we can pass xcss prop to it. We opt to do this instead of
|
|
73
|
+
// Making the output of cssMap() a string intersection so we can also have an inline object
|
|
74
|
+
// be declared.
|
|
75
|
+
/**
|
|
76
|
+
* The class name prop now can be given the output of xcss prop from `@compiled/react`.
|
|
77
|
+
*/
|
|
78
|
+
className?: string | Record<string, any> | null | false;
|
|
72
79
|
css?: CssFunction<void> | CssFunction<void>[];
|
|
73
80
|
};
|
|
74
81
|
};
|
package/src/runtime/ac.ts
CHANGED
|
@@ -58,7 +58,7 @@ class AtomicGroups {
|
|
|
58
58
|
* @param classes
|
|
59
59
|
*/
|
|
60
60
|
export function ac(
|
|
61
|
-
classNames: (AtomicGroups | string | undefined | false)[]
|
|
61
|
+
classNames: (AtomicGroups | string | null | undefined | false)[]
|
|
62
62
|
): AtomicGroups | undefined {
|
|
63
63
|
// short circuit if there's no class names.
|
|
64
64
|
if (classNames.length <= 1 && !classNames[0]) return undefined;
|
package/src/runtime/ax.ts
CHANGED
|
@@ -27,7 +27,7 @@ const ATOMIC_GROUP_LENGTH = 5;
|
|
|
27
27
|
*
|
|
28
28
|
* @param classes
|
|
29
29
|
*/
|
|
30
|
-
export default function ax(classNames: (string | undefined | false)[]): string | undefined {
|
|
30
|
+
export default function ax(classNames: (string | undefined | null | false)[]): string | undefined {
|
|
31
31
|
if (classNames.length <= 1 && (!classNames[0] || classNames[0].indexOf(' ') === -1)) {
|
|
32
32
|
// short circuit if there's no custom class names.
|
|
33
33
|
return classNames[0] || undefined;
|
package/src/types.ts
CHANGED
|
@@ -31,5 +31,73 @@ export type CssObject<TProps> = Readonly<{
|
|
|
31
31
|
export type CssFunction<TProps = unknown> =
|
|
32
32
|
| CssType<TProps>
|
|
33
33
|
| BasicTemplateInterpolations // CSS values in tagged template expression
|
|
34
|
+
| null
|
|
34
35
|
| boolean // Something like `false && styles`
|
|
35
36
|
| undefined; // Something like `undefined && styles`
|
|
37
|
+
|
|
38
|
+
/*
|
|
39
|
+
* This list of pseudo-classes and pseudo-elements are from csstype
|
|
40
|
+
* but with & added to the front. Compiled supports both &-ful
|
|
41
|
+
* and &-less forms and both will target the current element
|
|
42
|
+
* (`&:hover` <==> `:hover`), however we force the use of the
|
|
43
|
+
* &-ful form for consistency with the nested spec for new APIs.
|
|
44
|
+
*/
|
|
45
|
+
export type CSSPseudos =
|
|
46
|
+
| '&::after'
|
|
47
|
+
| '&::backdrop'
|
|
48
|
+
| '&::before'
|
|
49
|
+
| '&::cue'
|
|
50
|
+
| '&::cue-region'
|
|
51
|
+
| '&::first-letter'
|
|
52
|
+
| '&::first-line'
|
|
53
|
+
| '&::grammar-error'
|
|
54
|
+
| '&::marker'
|
|
55
|
+
| '&::placeholder'
|
|
56
|
+
| '&::selection'
|
|
57
|
+
| '&::spelling-error'
|
|
58
|
+
| '&::target-text'
|
|
59
|
+
| '&::view-transition'
|
|
60
|
+
| '&:active'
|
|
61
|
+
| '&:autofill'
|
|
62
|
+
| '&:blank'
|
|
63
|
+
| '&:checked'
|
|
64
|
+
| '&:default'
|
|
65
|
+
| '&:defined'
|
|
66
|
+
| '&:disabled'
|
|
67
|
+
| '&:empty'
|
|
68
|
+
| '&:enabled'
|
|
69
|
+
| '&:first'
|
|
70
|
+
| '&:focus'
|
|
71
|
+
| '&:focus-visible'
|
|
72
|
+
| '&:focus-within'
|
|
73
|
+
| '&:fullscreen'
|
|
74
|
+
| '&:hover'
|
|
75
|
+
| '&:in-range'
|
|
76
|
+
| '&:indeterminate'
|
|
77
|
+
| '&:invalid'
|
|
78
|
+
| '&:left'
|
|
79
|
+
| '&:link'
|
|
80
|
+
| '&:local-link'
|
|
81
|
+
| '&:optional'
|
|
82
|
+
| '&:out-of-range'
|
|
83
|
+
| '&:paused'
|
|
84
|
+
| '&:picture-in-picture'
|
|
85
|
+
| '&:placeholder-shown'
|
|
86
|
+
| '&:playing'
|
|
87
|
+
| '&:read-only'
|
|
88
|
+
| '&:read-write'
|
|
89
|
+
| '&:required'
|
|
90
|
+
| '&:right'
|
|
91
|
+
| '&:target'
|
|
92
|
+
| '&:user-invalid'
|
|
93
|
+
| '&:user-valid'
|
|
94
|
+
| '&:valid'
|
|
95
|
+
| '&:visited';
|
|
96
|
+
|
|
97
|
+
/**
|
|
98
|
+
* The xcss prop must be given all known available properties even
|
|
99
|
+
* if it takes a subset of them. This is ensure the (lack-of an)
|
|
100
|
+
* excess property check doesn't enable makers to circumvent the
|
|
101
|
+
* system and pass in values they shouldn't.
|
|
102
|
+
*/
|
|
103
|
+
export type CSSProperties = Readonly<CSS.Properties<string | number>>;
|
|
@@ -0,0 +1,294 @@
|
|
|
1
|
+
/** @jsxImportSource @compiled/react */
|
|
2
|
+
// eslint-disable-next-line import/no-extraneous-dependencies
|
|
3
|
+
import { cssMap, cx } from '@compiled/react';
|
|
4
|
+
import { render } from '@testing-library/react';
|
|
5
|
+
import { expectTypeOf } from 'expect-type';
|
|
6
|
+
|
|
7
|
+
import type { XCSSProp, XCSSAllProperties, XCSSAllPseudos } from '../index';
|
|
8
|
+
|
|
9
|
+
describe('xcss prop', () => {
|
|
10
|
+
it('should allow all styles from xcss prop to class name when no constraints are applied', () => {
|
|
11
|
+
function CSSPropComponent({ xcss }: { xcss: XCSSProp<XCSSAllProperties, XCSSAllPseudos> }) {
|
|
12
|
+
return <div className={xcss}>foo</div>;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
const styles = cssMap({
|
|
16
|
+
redColor: { color: 'red', '&::after': { backgroundColor: 'green' } },
|
|
17
|
+
});
|
|
18
|
+
|
|
19
|
+
const { getByText } = render(<CSSPropComponent xcss={styles.redColor} />);
|
|
20
|
+
|
|
21
|
+
expect(getByText('foo')).toHaveCompiledCss('color', 'red');
|
|
22
|
+
});
|
|
23
|
+
|
|
24
|
+
it('should type error when given a pseudo and none are allowed', () => {
|
|
25
|
+
function CSSPropComponent({ xcss }: { xcss: XCSSProp<XCSSAllProperties, never> }) {
|
|
26
|
+
return <div className={xcss}>foo</div>;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
const styles = cssMap({
|
|
30
|
+
redColor: { color: 'red', '&::after': { backgroundColor: 'green' } },
|
|
31
|
+
});
|
|
32
|
+
|
|
33
|
+
const { getByText } = render(
|
|
34
|
+
<CSSPropComponent
|
|
35
|
+
// @ts-expect-error — Types of property '"&::after"' are incompatible.
|
|
36
|
+
xcss={styles.redColor}
|
|
37
|
+
/>
|
|
38
|
+
);
|
|
39
|
+
|
|
40
|
+
expect(getByText('foo')).toHaveCompiledCss('color', 'red');
|
|
41
|
+
});
|
|
42
|
+
|
|
43
|
+
it('should concat styles from class name and xcss prop', () => {
|
|
44
|
+
function CSSPropComponent({ xcss }: { xcss: XCSSProp<XCSSAllProperties, XCSSAllPseudos> }) {
|
|
45
|
+
return (
|
|
46
|
+
<div css={{ color: 'blue' }} className={xcss}>
|
|
47
|
+
foo
|
|
48
|
+
</div>
|
|
49
|
+
);
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
const styles = cssMap({
|
|
53
|
+
redColor: { color: 'red' },
|
|
54
|
+
});
|
|
55
|
+
|
|
56
|
+
const { getByText } = render(<CSSPropComponent xcss={styles.redColor} />);
|
|
57
|
+
|
|
58
|
+
expect(getByText('foo')).toHaveCompiledCss('color', 'red');
|
|
59
|
+
});
|
|
60
|
+
|
|
61
|
+
it('should type error when passing styles that are not defined', () => {
|
|
62
|
+
function CSSPropComponent({ xcss }: { xcss: XCSSProp<'color', XCSSAllPseudos> }) {
|
|
63
|
+
return <div className={xcss}>foo</div>;
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
const styles = cssMap({
|
|
67
|
+
redColor: { backgroundColor: 'red' },
|
|
68
|
+
});
|
|
69
|
+
|
|
70
|
+
expectTypeOf(
|
|
71
|
+
<CSSPropComponent
|
|
72
|
+
// @ts-expect-error — Types of property 'backgroundColor' are incompatible.
|
|
73
|
+
xcss={styles.redColor}
|
|
74
|
+
/>
|
|
75
|
+
).toBeObject();
|
|
76
|
+
});
|
|
77
|
+
|
|
78
|
+
it('should concat styles together', () => {
|
|
79
|
+
function CSSPropComponent({ xcss }: { xcss: XCSSProp<XCSSAllProperties, XCSSAllPseudos> }) {
|
|
80
|
+
return <div className={xcss}>foo</div>;
|
|
81
|
+
}
|
|
82
|
+
const styles = cssMap({
|
|
83
|
+
redColor: { color: 'red' },
|
|
84
|
+
greenBackground: { color: 'blue', backgroundColor: 'green' },
|
|
85
|
+
});
|
|
86
|
+
|
|
87
|
+
const { getByText } = render(
|
|
88
|
+
<CSSPropComponent xcss={cx(styles.redColor, styles.greenBackground)} />
|
|
89
|
+
);
|
|
90
|
+
|
|
91
|
+
expect(getByText('foo')).toHaveCompiledCss('color', 'blue');
|
|
92
|
+
expect(getByText('foo')).toHaveCompiledCss('backgroundColor', 'green');
|
|
93
|
+
});
|
|
94
|
+
|
|
95
|
+
it('should conditionally apply styles directly', () => {
|
|
96
|
+
const styles = cssMap({
|
|
97
|
+
redColor: { color: 'red' },
|
|
98
|
+
blueColor: { color: 'blue' },
|
|
99
|
+
});
|
|
100
|
+
function CSSPropComponent({ xcss }: { xcss: XCSSProp<'color', never> }) {
|
|
101
|
+
return <div className={xcss}>foo</div>;
|
|
102
|
+
}
|
|
103
|
+
function Parent({ isRed }: { isRed: boolean }) {
|
|
104
|
+
return <CSSPropComponent xcss={isRed ? styles.redColor : styles.blueColor} />;
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
const { getByText, rerender } = render(<Parent isRed />);
|
|
108
|
+
|
|
109
|
+
expect(getByText('foo')).toHaveCompiledCss('color', 'red');
|
|
110
|
+
expect(getByText('foo')).not.toHaveCompiledCss('color', 'blue');
|
|
111
|
+
|
|
112
|
+
rerender(<Parent isRed={false} />);
|
|
113
|
+
|
|
114
|
+
expect(getByText('foo')).toHaveCompiledCss('color', 'blue');
|
|
115
|
+
expect(getByText('foo')).not.toHaveCompiledCss('color', 'red');
|
|
116
|
+
});
|
|
117
|
+
|
|
118
|
+
it('should conditionally apply styles via cx function', () => {
|
|
119
|
+
const styles = cssMap({
|
|
120
|
+
redColor: { color: 'red' },
|
|
121
|
+
blueColor: { color: 'blue' },
|
|
122
|
+
});
|
|
123
|
+
function CSSPropComponent({ xcss }: { xcss: XCSSProp<'color', never> }) {
|
|
124
|
+
return <div className={xcss}>foo</div>;
|
|
125
|
+
}
|
|
126
|
+
function Parent({ isRed }: { isRed: boolean }) {
|
|
127
|
+
return <CSSPropComponent xcss={cx(isRed && styles.redColor, !isRed && styles.blueColor)} />;
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
const { getByText, rerender } = render(<Parent isRed />);
|
|
131
|
+
|
|
132
|
+
expect(getByText('foo')).toHaveCompiledCss('color', 'red');
|
|
133
|
+
expect(getByText('foo')).not.toHaveCompiledCss('color', 'blue');
|
|
134
|
+
|
|
135
|
+
rerender(<Parent isRed={false} />);
|
|
136
|
+
|
|
137
|
+
expect(getByText('foo')).toHaveCompiledCss('color', 'blue');
|
|
138
|
+
expect(getByText('foo')).not.toHaveCompiledCss('color', 'red');
|
|
139
|
+
});
|
|
140
|
+
|
|
141
|
+
it('should transform inline object', () => {
|
|
142
|
+
function CSSPropComponent({ xcss }: { xcss: XCSSProp<'color', XCSSAllPseudos> }) {
|
|
143
|
+
return <div className={xcss}>foo</div>;
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
const { getByText } = render(<CSSPropComponent xcss={{ color: 'green' }} />);
|
|
147
|
+
|
|
148
|
+
expect(getByText('foo')).toHaveCompiledCss('color', 'green');
|
|
149
|
+
});
|
|
150
|
+
|
|
151
|
+
it('should type error when passing in a disallowed value in a pseudo mixed with allowed values', () => {
|
|
152
|
+
function CSSPropComponent({ xcss }: { xcss: XCSSProp<'color', '&:hover'> }) {
|
|
153
|
+
return <div className={xcss}>foo</div>;
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
const styles = cssMap({
|
|
157
|
+
redColor: { color: 'red', '&:hover': { color: 'red', backgroundColor: 'red' } },
|
|
158
|
+
});
|
|
159
|
+
|
|
160
|
+
expectTypeOf(
|
|
161
|
+
<CSSPropComponent
|
|
162
|
+
// @ts-expect-error — Types of property 'backgroundColor' are incompatible.
|
|
163
|
+
xcss={styles.redColor}
|
|
164
|
+
/>
|
|
165
|
+
).toBeObject();
|
|
166
|
+
expectTypeOf(
|
|
167
|
+
<CSSPropComponent
|
|
168
|
+
xcss={{
|
|
169
|
+
color: 'red',
|
|
170
|
+
'&:hover': {
|
|
171
|
+
color: 'red',
|
|
172
|
+
// @ts-expect-error — Types of property 'backgroundColor' are incompatible.
|
|
173
|
+
backgroundColor: 'red',
|
|
174
|
+
},
|
|
175
|
+
}}
|
|
176
|
+
/>
|
|
177
|
+
).toBeObject();
|
|
178
|
+
});
|
|
179
|
+
|
|
180
|
+
it('should type error when passing in at rules to xcss prop', () => {
|
|
181
|
+
function CSSPropComponent({ xcss }: { xcss: XCSSProp<'color', '&:hover'> }) {
|
|
182
|
+
return <div className={xcss}>foo</div>;
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
const styles = cssMap({
|
|
186
|
+
redColor: { color: 'red', '@media': { 'screen and': { color: 'red' } } },
|
|
187
|
+
});
|
|
188
|
+
|
|
189
|
+
expectTypeOf(
|
|
190
|
+
<CSSPropComponent
|
|
191
|
+
// @ts-expect-error — Types of property '"@media"' are incompatible.
|
|
192
|
+
xcss={styles.redColor}
|
|
193
|
+
/>
|
|
194
|
+
).toBeObject();
|
|
195
|
+
expectTypeOf(
|
|
196
|
+
<CSSPropComponent
|
|
197
|
+
xcss={{
|
|
198
|
+
color: 'red',
|
|
199
|
+
// @ts-expect-error — Type '{ screen: { color: string; backgroundColor: string; }; }' is not assignable to type 'undefined'.
|
|
200
|
+
'@media': {
|
|
201
|
+
screen: { color: 'red', backgroundColor: 'red' },
|
|
202
|
+
},
|
|
203
|
+
}}
|
|
204
|
+
/>
|
|
205
|
+
).toBeObject();
|
|
206
|
+
});
|
|
207
|
+
|
|
208
|
+
it('should block selectors api from CSS Map', () => {
|
|
209
|
+
function CSSPropComponent({ xcss }: { xcss: XCSSProp<'color', '&:hover'> }) {
|
|
210
|
+
return <div className={xcss}>foo</div>;
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
const styles = cssMap({
|
|
214
|
+
primary: {
|
|
215
|
+
selectors: { '&:not(:first-child):last-child': { color: 'red' } },
|
|
216
|
+
},
|
|
217
|
+
});
|
|
218
|
+
|
|
219
|
+
expectTypeOf(
|
|
220
|
+
<CSSPropComponent
|
|
221
|
+
// @ts-expect-error — Type 'CompiledStyles<{ '&:not(:first-child):last-child': { color: "red"; }; }>' is not assignable to type 'undefined'.
|
|
222
|
+
xcss={styles.primary}
|
|
223
|
+
/>
|
|
224
|
+
).toBeObject();
|
|
225
|
+
});
|
|
226
|
+
|
|
227
|
+
it('should mark a xcss prop as required', () => {
|
|
228
|
+
function CSSPropComponent({
|
|
229
|
+
xcss,
|
|
230
|
+
}: {
|
|
231
|
+
xcss: XCSSProp<
|
|
232
|
+
'color' | 'backgroundColor',
|
|
233
|
+
'&:hover',
|
|
234
|
+
{ requiredProperties: 'color'; requiredPseudos: never }
|
|
235
|
+
>;
|
|
236
|
+
}) {
|
|
237
|
+
return <div className={xcss}>foo</div>;
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
expectTypeOf(
|
|
241
|
+
<CSSPropComponent
|
|
242
|
+
// @ts-expect-error — Type '{}' is not assignable to type 'XCSSProp<"backgroundColor" | "color", "&:hover", { requiredProperties: "color"; }>'.
|
|
243
|
+
xcss={{}}
|
|
244
|
+
/>
|
|
245
|
+
).toBeObject();
|
|
246
|
+
});
|
|
247
|
+
|
|
248
|
+
it('should mark a xcss prop inside a pseudo as required', () => {
|
|
249
|
+
function CSSPropComponent({
|
|
250
|
+
xcss,
|
|
251
|
+
}: {
|
|
252
|
+
xcss: XCSSProp<
|
|
253
|
+
'color' | 'backgroundColor',
|
|
254
|
+
'&:hover',
|
|
255
|
+
{ requiredProperties: 'color'; requiredPseudos: never }
|
|
256
|
+
>;
|
|
257
|
+
}) {
|
|
258
|
+
return <div className={xcss}>foo</div>;
|
|
259
|
+
}
|
|
260
|
+
|
|
261
|
+
expectTypeOf(
|
|
262
|
+
<CSSPropComponent
|
|
263
|
+
xcss={{
|
|
264
|
+
color: 'red',
|
|
265
|
+
// @ts-expect-error — Property 'color' is missing in type '{}' but required in type '{ readonly color: string | number | CompiledPropertyDeclarationReference; }'.
|
|
266
|
+
'&:hover': {},
|
|
267
|
+
}}
|
|
268
|
+
/>
|
|
269
|
+
).toBeObject();
|
|
270
|
+
});
|
|
271
|
+
|
|
272
|
+
it('should mark a xcss prop pseudo as required', () => {
|
|
273
|
+
function CSSPropComponent({
|
|
274
|
+
xcss,
|
|
275
|
+
}: {
|
|
276
|
+
xcss: XCSSProp<
|
|
277
|
+
'color' | 'backgroundColor',
|
|
278
|
+
'&:hover',
|
|
279
|
+
{ requiredProperties: never; requiredPseudos: '&:hover' }
|
|
280
|
+
>;
|
|
281
|
+
}) {
|
|
282
|
+
return <div className={xcss}>foo</div>;
|
|
283
|
+
}
|
|
284
|
+
|
|
285
|
+
expectTypeOf(
|
|
286
|
+
<CSSPropComponent
|
|
287
|
+
// @ts-expect-error — Property '"&:hover"' is missing in type '{ color: string; }' but required in type '{ "&:hover": MarkAsRequired<XCSSItem<"backgroundColor" | "color">, never>; }'.
|
|
288
|
+
xcss={{
|
|
289
|
+
color: 'red',
|
|
290
|
+
}}
|
|
291
|
+
/>
|
|
292
|
+
).toBeObject();
|
|
293
|
+
});
|
|
294
|
+
});
|
|
@@ -0,0 +1,174 @@
|
|
|
1
|
+
import type * as CSS from 'csstype';
|
|
2
|
+
|
|
3
|
+
import { ac } from '../runtime';
|
|
4
|
+
import type { CSSPseudos, CSSProperties } from '../types';
|
|
5
|
+
|
|
6
|
+
type XCSSItem<TStyleDecl extends keyof CSSProperties> = {
|
|
7
|
+
[Q in keyof CSSProperties]: Q extends TStyleDecl
|
|
8
|
+
? CompiledPropertyDeclarationReference | string | number
|
|
9
|
+
: never;
|
|
10
|
+
};
|
|
11
|
+
|
|
12
|
+
type XCSSPseudos<
|
|
13
|
+
TAllowedProperties extends keyof CSSProperties,
|
|
14
|
+
TAllowedPseudos extends CSSPseudos,
|
|
15
|
+
TRequiredProperties extends { requiredProperties: TAllowedProperties }
|
|
16
|
+
> = {
|
|
17
|
+
[Q in CSSPseudos]?: Q extends TAllowedPseudos
|
|
18
|
+
? MarkAsRequired<XCSSItem<TAllowedProperties>, TRequiredProperties['requiredProperties']>
|
|
19
|
+
: never;
|
|
20
|
+
};
|
|
21
|
+
|
|
22
|
+
/**
|
|
23
|
+
* These APIs we don't want to allow to be passed through the `xcss` prop but we also
|
|
24
|
+
* must declare them so the (lack-of a) excess property check doesn't bite us and allow
|
|
25
|
+
* unexpected values through.
|
|
26
|
+
*/
|
|
27
|
+
type BlockedRules = {
|
|
28
|
+
selectors?: never;
|
|
29
|
+
} & {
|
|
30
|
+
/**
|
|
31
|
+
* We currently block all at rules from xcss prop.
|
|
32
|
+
* This needs us to decide on what the final API is across Compiled to be able to set.
|
|
33
|
+
*/
|
|
34
|
+
[Q in CSS.AtRules]?: never;
|
|
35
|
+
};
|
|
36
|
+
|
|
37
|
+
type CompiledPropertyDeclarationReference = {
|
|
38
|
+
['__COMPILED_PROPERTY_DECLARATION_REFERENCE_DO_NOT_WRITE_DIRECTLY__']: true;
|
|
39
|
+
};
|
|
40
|
+
|
|
41
|
+
/**
|
|
42
|
+
* Used to mark styles that have been flushed through an API as being generated
|
|
43
|
+
* from Compiled. This is useful when you want other ends of the API to ensure they
|
|
44
|
+
* take Compiled generated styles and not some arbitrary object.
|
|
45
|
+
*/
|
|
46
|
+
export type CompiledStyles<TObject> = {
|
|
47
|
+
[Q in keyof TObject]: TObject[Q] extends Record<string, unknown>
|
|
48
|
+
? CompiledStyles<TObject[Q]>
|
|
49
|
+
: CompiledPropertyDeclarationReference;
|
|
50
|
+
};
|
|
51
|
+
|
|
52
|
+
/**
|
|
53
|
+
* Please think twice before using this type, you're better off declaring explicitly
|
|
54
|
+
* what your API should be, for example only defining `"color"`.
|
|
55
|
+
*
|
|
56
|
+
* Use in conjunction with {@link XCSSProp} to allow all properties to be given to
|
|
57
|
+
* your component.
|
|
58
|
+
*/
|
|
59
|
+
export type XCSSAllProperties = keyof CSSProperties;
|
|
60
|
+
|
|
61
|
+
/**
|
|
62
|
+
* Please think twice before using this type, you're better off declaring explicitly
|
|
63
|
+
* what your API should be, for example not allowing any pseudos at all using the
|
|
64
|
+
* `never` type.
|
|
65
|
+
*
|
|
66
|
+
* Use in conjunction with {@link XCSSProp} to allow all pseudos to be given to
|
|
67
|
+
* your component.
|
|
68
|
+
*/
|
|
69
|
+
export type XCSSAllPseudos = CSSPseudos;
|
|
70
|
+
|
|
71
|
+
/**
|
|
72
|
+
* ## xcss prop
|
|
73
|
+
*
|
|
74
|
+
* Declare styles your component takes with all other styles marked as violations
|
|
75
|
+
* by the TypeScript compiler. There are two primary use cases for xcss prop:
|
|
76
|
+
*
|
|
77
|
+
* - safe style overrides
|
|
78
|
+
* - inverting style declarations
|
|
79
|
+
*
|
|
80
|
+
* Interverting style declarations is interesting for platform teams as
|
|
81
|
+
* it means products only pay for styles they use as they're now the ones who declare
|
|
82
|
+
* the styles!
|
|
83
|
+
*
|
|
84
|
+
* The {@link XCSSProp} type has generics two of which must be defined — use to explicitly
|
|
85
|
+
* set want you to maintain as API. Use {@link XCSSAllProperties} and {@link XCSSAllPseudos}
|
|
86
|
+
* to enable all properties and pseudos.
|
|
87
|
+
*
|
|
88
|
+
* The third generic is used to declare what properties and pseudos should be required.
|
|
89
|
+
*
|
|
90
|
+
* @example
|
|
91
|
+
* ```
|
|
92
|
+
* interface MyComponentProps {
|
|
93
|
+
* // Color is accepted, all other properties / pseudos are considered violations.
|
|
94
|
+
* xcss?: XCSSProp<'color', never>;
|
|
95
|
+
*
|
|
96
|
+
* // Only backgrond color and hover pseudo is accepted.
|
|
97
|
+
* xcss?: XCSSProp<'backgroundColor', '&:hover'>;
|
|
98
|
+
*
|
|
99
|
+
* // All properties are accepted, all pseudos are considered violations.
|
|
100
|
+
* xcss?: XCSSProp<XCSSAllProperties, never>;
|
|
101
|
+
*
|
|
102
|
+
* // All properties are accepted, only the hover pseudo is accepted.
|
|
103
|
+
* xcss?: XCSSProp<XCSSAllProperties, '&:hover'>;
|
|
104
|
+
*
|
|
105
|
+
* // The xcss prop is required as well as the color property. No pseudos are required.
|
|
106
|
+
* xcss: XCSSProp<XCSSAllProperties, '&:hover', { requiredProperties: 'color', requiredPseudos: never }>;
|
|
107
|
+
* }
|
|
108
|
+
*
|
|
109
|
+
* function MyComponent({ xcss }: MyComponentProps) {
|
|
110
|
+
* return <div css={{ color: 'var(--ds-text-danger)' }} className={xcss} />
|
|
111
|
+
* }
|
|
112
|
+
* ```
|
|
113
|
+
*
|
|
114
|
+
* The xcss prop works with static inline objects and the [cssMap](https://compiledcssinjs.com/docs/api-cssmap) API.
|
|
115
|
+
*
|
|
116
|
+
* @example
|
|
117
|
+
* ```
|
|
118
|
+
* // Declared as an inline object
|
|
119
|
+
* <Component xcss={{ color: 'var(--ds-text)' }} />
|
|
120
|
+
*
|
|
121
|
+
* // Declared with the cssMap API
|
|
122
|
+
* const styles = cssMap({ text: { color: 'var(--ds-text)' } });
|
|
123
|
+
* <Component xcss={styles.text} />
|
|
124
|
+
* ```
|
|
125
|
+
*
|
|
126
|
+
* To concatenate and conditonally apply styles use the {@link cssMap} {@link cx} functions.
|
|
127
|
+
*/
|
|
128
|
+
export type XCSSProp<
|
|
129
|
+
TAllowedProperties extends keyof CSSProperties,
|
|
130
|
+
TAllowedPseudos extends CSSPseudos,
|
|
131
|
+
TRequiredProperties extends {
|
|
132
|
+
requiredProperties: TAllowedProperties;
|
|
133
|
+
requiredPseudos: TAllowedPseudos;
|
|
134
|
+
} = never
|
|
135
|
+
> =
|
|
136
|
+
| (MarkAsRequired<XCSSItem<TAllowedProperties>, TRequiredProperties['requiredProperties']> &
|
|
137
|
+
MarkAsRequired<
|
|
138
|
+
XCSSPseudos<TAllowedProperties, TAllowedPseudos, TRequiredProperties>,
|
|
139
|
+
TRequiredProperties['requiredPseudos']
|
|
140
|
+
> &
|
|
141
|
+
BlockedRules)
|
|
142
|
+
| false
|
|
143
|
+
| null
|
|
144
|
+
| undefined;
|
|
145
|
+
|
|
146
|
+
type MarkAsRequired<T, K extends keyof T> = T & { [P in K]-?: T[P] };
|
|
147
|
+
|
|
148
|
+
/**
|
|
149
|
+
* ## cx
|
|
150
|
+
*
|
|
151
|
+
* Use in conjunction with the {@link XCSSProp} to concatenate and conditionally apply
|
|
152
|
+
* declared styles. Can only be used with the `cssMap()` and {@link XCSSProp} APIs.
|
|
153
|
+
*
|
|
154
|
+
* @example
|
|
155
|
+
* ```
|
|
156
|
+
* const styles = cssMap({
|
|
157
|
+
* text: { color: 'var(--ds-text)' },
|
|
158
|
+
* primary: { color: 'var(--ds-text-brand)' },
|
|
159
|
+
* });
|
|
160
|
+
*
|
|
161
|
+
* <Component xcss={cx(isPrimary && styles.text, !isPrimary && styles.primary)} />
|
|
162
|
+
* ```
|
|
163
|
+
*/
|
|
164
|
+
export const cx = <TStyles extends [...XCSSProp<any, any>[]]>(
|
|
165
|
+
...styles: TStyles
|
|
166
|
+
): TStyles[number] => {
|
|
167
|
+
// At runtime TStyles is resolved down to strings, but not at compile time.
|
|
168
|
+
// We circumvent the type system here because of that.
|
|
169
|
+
const actualStyles = styles as unknown as string[];
|
|
170
|
+
|
|
171
|
+
// The output should be a union type of passed in styles. This ensures the call
|
|
172
|
+
// site of xcss prop can raise violations when disallowed styles have been passed.
|
|
173
|
+
return ac(actualStyles) as TStyles[number];
|
|
174
|
+
};
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
export type Pseudos = '&::after' | '&::backdrop' | '&::before' | '&::cue' | '&::cue-region' | '&::first-letter' | '&::first-line' | '&::grammar-error' | '&::marker' | '&::placeholder' | '&::selection' | '&::spelling-error' | '&::target-text' | '&::view-transition' | '&:active' | '&:autofill' | '&:blank' | '&:checked' | '&:default' | '&:defined' | '&:disabled' | '&:empty' | '&:enabled' | '&:first' | '&:focus' | '&:focus-visible' | '&:focus-within' | '&:fullscreen' | '&:hover' | '&:in-range' | '&:indeterminate' | '&:invalid' | '&:left' | '&:link' | '&:local-link' | '&:optional' | '&:out-of-range' | '&:paused' | '&:picture-in-picture' | '&:placeholder-shown' | '&:playing' | '&:read-only' | '&:read-write' | '&:required' | '&:right' | '&:target' | '&:user-invalid' | '&:user-valid' | '&:valid' | '&:visited';
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"pseudos.js","sourceRoot":"","sources":["../../../src/css-map/pseudos.ts"],"names":[],"mappings":"AAAA,8DAA8D;AAC9D,uEAAuE;AACvE,+BAA+B"}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
export type Pseudos = '&::after' | '&::backdrop' | '&::before' | '&::cue' | '&::cue-region' | '&::first-letter' | '&::first-line' | '&::grammar-error' | '&::marker' | '&::placeholder' | '&::selection' | '&::spelling-error' | '&::target-text' | '&::view-transition' | '&:active' | '&:autofill' | '&:blank' | '&:checked' | '&:default' | '&:defined' | '&:disabled' | '&:empty' | '&:enabled' | '&:first' | '&:focus' | '&:focus-visible' | '&:focus-within' | '&:fullscreen' | '&:hover' | '&:in-range' | '&:indeterminate' | '&:invalid' | '&:left' | '&:link' | '&:local-link' | '&:optional' | '&:out-of-range' | '&:paused' | '&:picture-in-picture' | '&:placeholder-shown' | '&:playing' | '&:read-only' | '&:read-write' | '&:required' | '&:right' | '&:target' | '&:user-invalid' | '&:user-valid' | '&:valid' | '&:visited';
|
|
@@ -1,6 +0,0 @@
|
|
|
1
|
-
"use strict";
|
|
2
|
-
// List of pseudo-classes and pseudo-elements are from csstype
|
|
3
|
-
// but with & added in the front, so that we target the current element
|
|
4
|
-
// (instead of a child element)
|
|
5
|
-
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
-
//# sourceMappingURL=pseudos.js.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"pseudos.js","sourceRoot":"","sources":["../../../src/css-map/pseudos.ts"],"names":[],"mappings":";AAAA,8DAA8D;AAC9D,uEAAuE;AACvE,+BAA+B"}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
export type Pseudos = '&::after' | '&::backdrop' | '&::before' | '&::cue' | '&::cue-region' | '&::first-letter' | '&::first-line' | '&::grammar-error' | '&::marker' | '&::placeholder' | '&::selection' | '&::spelling-error' | '&::target-text' | '&::view-transition' | '&:active' | '&:autofill' | '&:blank' | '&:checked' | '&:default' | '&:defined' | '&:disabled' | '&:empty' | '&:enabled' | '&:first' | '&:focus' | '&:focus-visible' | '&:focus-within' | '&:fullscreen' | '&:hover' | '&:in-range' | '&:indeterminate' | '&:invalid' | '&:left' | '&:link' | '&:local-link' | '&:optional' | '&:out-of-range' | '&:paused' | '&:picture-in-picture' | '&:placeholder-shown' | '&:playing' | '&:read-only' | '&:read-write' | '&:required' | '&:right' | '&:target' | '&:user-invalid' | '&:user-valid' | '&:valid' | '&:visited';
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"pseudos.js","sourceRoot":"","sources":["../../../src/css-map/pseudos.ts"],"names":[],"mappings":"AAAA,8DAA8D;AAC9D,uEAAuE;AACvE,+BAA+B"}
|