@react-spectrum/s2 3.0.0-nightly-63d4359d6-250428 → 3.0.0-nightly-1dd65ffc5-250430

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.
Files changed (365) hide show
  1. package/dist/Accordion.cjs +4 -5
  2. package/dist/Accordion.cjs.map +1 -1
  3. package/dist/Accordion.css +2 -4
  4. package/dist/Accordion.css.map +1 -1
  5. package/dist/Accordion.mjs +4 -5
  6. package/dist/Accordion.mjs.map +1 -1
  7. package/dist/ActionBar.cjs +72 -63
  8. package/dist/ActionBar.cjs.map +1 -1
  9. package/dist/ActionBar.css +70 -63
  10. package/dist/ActionBar.css.map +1 -1
  11. package/dist/ActionBar.mjs +72 -63
  12. package/dist/ActionBar.mjs.map +1 -1
  13. package/dist/ActionButton.cjs +161 -160
  14. package/dist/ActionButton.cjs.map +1 -1
  15. package/dist/ActionButton.css +174 -168
  16. package/dist/ActionButton.css.map +1 -1
  17. package/dist/ActionButton.mjs +161 -160
  18. package/dist/ActionButton.mjs.map +1 -1
  19. package/dist/ActionButtonGroup.cjs +17 -18
  20. package/dist/ActionButtonGroup.cjs.map +1 -1
  21. package/dist/ActionButtonGroup.css +9 -11
  22. package/dist/ActionButtonGroup.css.map +1 -1
  23. package/dist/ActionButtonGroup.mjs +17 -18
  24. package/dist/ActionButtonGroup.mjs.map +1 -1
  25. package/dist/AlertDialog.cjs +4 -4
  26. package/dist/AlertDialog.css +3 -5
  27. package/dist/AlertDialog.css.map +1 -1
  28. package/dist/AlertDialog.mjs +4 -4
  29. package/dist/Avatar.cjs +21 -21
  30. package/dist/Avatar.css +14 -16
  31. package/dist/Avatar.css.map +1 -1
  32. package/dist/Avatar.mjs +21 -21
  33. package/dist/AvatarGroup.cjs +131 -110
  34. package/dist/AvatarGroup.cjs.map +1 -1
  35. package/dist/AvatarGroup.css +43 -49
  36. package/dist/AvatarGroup.css.map +1 -1
  37. package/dist/AvatarGroup.mjs +131 -110
  38. package/dist/AvatarGroup.mjs.map +1 -1
  39. package/dist/Badge.cjs +150 -149
  40. package/dist/Badge.cjs.map +1 -1
  41. package/dist/Badge.css +140 -134
  42. package/dist/Badge.css.map +1 -1
  43. package/dist/Badge.mjs +150 -149
  44. package/dist/Badge.mjs.map +1 -1
  45. package/dist/Breadcrumbs.cjs +169 -154
  46. package/dist/Breadcrumbs.cjs.map +1 -1
  47. package/dist/Breadcrumbs.css +119 -105
  48. package/dist/Breadcrumbs.css.map +1 -1
  49. package/dist/Breadcrumbs.mjs +169 -154
  50. package/dist/Breadcrumbs.mjs.map +1 -1
  51. package/dist/Button.cjs +321 -275
  52. package/dist/Button.cjs.map +1 -1
  53. package/dist/Button.css +245 -215
  54. package/dist/Button.css.map +1 -1
  55. package/dist/Button.mjs +321 -275
  56. package/dist/Button.mjs.map +1 -1
  57. package/dist/ButtonGroup.cjs +27 -25
  58. package/dist/ButtonGroup.cjs.map +1 -1
  59. package/dist/ButtonGroup.css +15 -17
  60. package/dist/ButtonGroup.css.map +1 -1
  61. package/dist/ButtonGroup.mjs +27 -25
  62. package/dist/ButtonGroup.mjs.map +1 -1
  63. package/dist/Card.cjs +316 -299
  64. package/dist/Card.cjs.map +1 -1
  65. package/dist/Card.css +242 -215
  66. package/dist/Card.css.map +1 -1
  67. package/dist/Card.mjs +316 -299
  68. package/dist/Card.mjs.map +1 -1
  69. package/dist/CardView.cjs +29 -30
  70. package/dist/CardView.cjs.map +1 -1
  71. package/dist/CardView.css +19 -19
  72. package/dist/CardView.mjs +29 -30
  73. package/dist/CardView.mjs.map +1 -1
  74. package/dist/CenterBaseline.cjs +2 -2
  75. package/dist/CenterBaseline.css +3 -5
  76. package/dist/CenterBaseline.css.map +1 -1
  77. package/dist/CenterBaseline.mjs +2 -2
  78. package/dist/Checkbox.cjs +114 -112
  79. package/dist/Checkbox.cjs.map +1 -1
  80. package/dist/Checkbox.css +99 -93
  81. package/dist/Checkbox.css.map +1 -1
  82. package/dist/Checkbox.mjs +114 -112
  83. package/dist/Checkbox.mjs.map +1 -1
  84. package/dist/CheckboxGroup.cjs +54 -54
  85. package/dist/CheckboxGroup.css +42 -42
  86. package/dist/CheckboxGroup.mjs +54 -54
  87. package/dist/ClearButton.cjs +19 -19
  88. package/dist/ClearButton.css +19 -21
  89. package/dist/ClearButton.css.map +1 -1
  90. package/dist/ClearButton.mjs +19 -19
  91. package/dist/CloseButton.cjs +64 -62
  92. package/dist/CloseButton.cjs.map +1 -1
  93. package/dist/CloseButton.css +48 -48
  94. package/dist/CloseButton.mjs +64 -62
  95. package/dist/CloseButton.mjs.map +1 -1
  96. package/dist/ColorArea.cjs +28 -28
  97. package/dist/ColorArea.css +17 -17
  98. package/dist/ColorArea.mjs +28 -28
  99. package/dist/ColorField.cjs +41 -41
  100. package/dist/ColorField.css +33 -33
  101. package/dist/ColorField.mjs +41 -41
  102. package/dist/ColorHandle.cjs +29 -29
  103. package/dist/ColorHandle.css +45 -45
  104. package/dist/ColorHandle.mjs +29 -29
  105. package/dist/ColorSlider.cjs +68 -64
  106. package/dist/ColorSlider.cjs.map +1 -1
  107. package/dist/ColorSlider.css +64 -58
  108. package/dist/ColorSlider.css.map +1 -1
  109. package/dist/ColorSlider.mjs +68 -64
  110. package/dist/ColorSlider.mjs.map +1 -1
  111. package/dist/ColorSwatch.cjs +44 -38
  112. package/dist/ColorSwatch.cjs.map +1 -1
  113. package/dist/ColorSwatch.css +29 -31
  114. package/dist/ColorSwatch.css.map +1 -1
  115. package/dist/ColorSwatch.mjs +44 -38
  116. package/dist/ColorSwatch.mjs.map +1 -1
  117. package/dist/ColorSwatchPicker.cjs +31 -32
  118. package/dist/ColorSwatchPicker.cjs.map +1 -1
  119. package/dist/ColorSwatchPicker.css +48 -48
  120. package/dist/ColorSwatchPicker.css.map +1 -1
  121. package/dist/ColorSwatchPicker.mjs +31 -32
  122. package/dist/ColorSwatchPicker.mjs.map +1 -1
  123. package/dist/ColorWheel.cjs +28 -28
  124. package/dist/ColorWheel.css +18 -18
  125. package/dist/ColorWheel.mjs +28 -28
  126. package/dist/ComboBox.cjs +108 -108
  127. package/dist/ComboBox.css +101 -101
  128. package/dist/ComboBox.mjs +108 -108
  129. package/dist/ContextualHelp.cjs +5 -5
  130. package/dist/ContextualHelp.css +58 -44
  131. package/dist/ContextualHelp.css.map +1 -1
  132. package/dist/ContextualHelp.mjs +5 -5
  133. package/dist/CustomDialog.cjs +38 -38
  134. package/dist/CustomDialog.css +26 -26
  135. package/dist/CustomDialog.mjs +38 -38
  136. package/dist/Dialog.cjs +18 -18
  137. package/dist/Dialog.css +86 -72
  138. package/dist/Dialog.css.map +1 -1
  139. package/dist/Dialog.mjs +18 -18
  140. package/dist/Disclosure.cjs +139 -133
  141. package/dist/Disclosure.cjs.map +1 -1
  142. package/dist/Disclosure.css +144 -120
  143. package/dist/Disclosure.css.map +1 -1
  144. package/dist/Disclosure.mjs +139 -133
  145. package/dist/Disclosure.mjs.map +1 -1
  146. package/dist/Divider.cjs +47 -47
  147. package/dist/Divider.css +26 -26
  148. package/dist/Divider.mjs +47 -47
  149. package/dist/DropZone.cjs +68 -64
  150. package/dist/DropZone.cjs.map +1 -1
  151. package/dist/DropZone.css +66 -60
  152. package/dist/DropZone.css.map +1 -1
  153. package/dist/DropZone.mjs +68 -64
  154. package/dist/DropZone.mjs.map +1 -1
  155. package/dist/Field.cjs +192 -186
  156. package/dist/Field.cjs.map +1 -1
  157. package/dist/Field.css +167 -159
  158. package/dist/Field.css.map +1 -1
  159. package/dist/Field.mjs +192 -186
  160. package/dist/Field.mjs.map +1 -1
  161. package/dist/Form.cjs +11 -12
  162. package/dist/Form.cjs.map +1 -1
  163. package/dist/Form.css +9 -11
  164. package/dist/Form.css.map +1 -1
  165. package/dist/Form.mjs +11 -12
  166. package/dist/Form.mjs.map +1 -1
  167. package/dist/FullscreenDialog.cjs +5 -5
  168. package/dist/FullscreenDialog.css +94 -80
  169. package/dist/FullscreenDialog.css.map +1 -1
  170. package/dist/FullscreenDialog.mjs +5 -5
  171. package/dist/IllustratedMessage.cjs +185 -160
  172. package/dist/IllustratedMessage.cjs.map +1 -1
  173. package/dist/IllustratedMessage.css +93 -91
  174. package/dist/IllustratedMessage.css.map +1 -1
  175. package/dist/IllustratedMessage.mjs +185 -160
  176. package/dist/IllustratedMessage.mjs.map +1 -1
  177. package/dist/Image.cjs +14 -14
  178. package/dist/Image.css +14 -16
  179. package/dist/Image.css.map +1 -1
  180. package/dist/Image.mjs +14 -14
  181. package/dist/InlineAlert.cjs +130 -122
  182. package/dist/InlineAlert.cjs.map +1 -1
  183. package/dist/InlineAlert.css +109 -95
  184. package/dist/InlineAlert.css.map +1 -1
  185. package/dist/InlineAlert.mjs +130 -122
  186. package/dist/InlineAlert.mjs.map +1 -1
  187. package/dist/Link.cjs +57 -47
  188. package/dist/Link.cjs.map +1 -1
  189. package/dist/Link.css +52 -44
  190. package/dist/Link.css.map +1 -1
  191. package/dist/Link.mjs +57 -47
  192. package/dist/Link.mjs.map +1 -1
  193. package/dist/Menu.cjs +353 -325
  194. package/dist/Menu.cjs.map +1 -1
  195. package/dist/Menu.css +189 -189
  196. package/dist/Menu.css.map +1 -1
  197. package/dist/Menu.mjs +353 -325
  198. package/dist/Menu.mjs.map +1 -1
  199. package/dist/Meter.cjs +116 -114
  200. package/dist/Meter.cjs.map +1 -1
  201. package/dist/Meter.css +106 -104
  202. package/dist/Meter.css.map +1 -1
  203. package/dist/Meter.mjs +116 -114
  204. package/dist/Meter.mjs.map +1 -1
  205. package/dist/Modal.cjs +62 -59
  206. package/dist/Modal.cjs.map +1 -1
  207. package/dist/Modal.css +52 -51
  208. package/dist/Modal.css.map +1 -1
  209. package/dist/Modal.mjs +62 -59
  210. package/dist/Modal.mjs.map +1 -1
  211. package/dist/NotificationBadge.cjs +66 -62
  212. package/dist/NotificationBadge.cjs.map +1 -1
  213. package/dist/NotificationBadge.css +78 -72
  214. package/dist/NotificationBadge.css.map +1 -1
  215. package/dist/NotificationBadge.mjs +66 -62
  216. package/dist/NotificationBadge.mjs.map +1 -1
  217. package/dist/NumberField.cjs +133 -133
  218. package/dist/NumberField.css +120 -120
  219. package/dist/NumberField.mjs +133 -133
  220. package/dist/Picker.cjs +209 -207
  221. package/dist/Picker.cjs.map +1 -1
  222. package/dist/Picker.css +179 -173
  223. package/dist/Picker.css.map +1 -1
  224. package/dist/Picker.mjs +209 -207
  225. package/dist/Picker.mjs.map +1 -1
  226. package/dist/Popover.cjs +124 -93
  227. package/dist/Popover.cjs.map +1 -1
  228. package/dist/Popover.css +67 -83
  229. package/dist/Popover.css.map +1 -1
  230. package/dist/Popover.mjs +124 -93
  231. package/dist/Popover.mjs.map +1 -1
  232. package/dist/ProgressBar.cjs +132 -130
  233. package/dist/ProgressBar.cjs.map +1 -1
  234. package/dist/ProgressBar.css +119 -117
  235. package/dist/ProgressBar.css.map +1 -1
  236. package/dist/ProgressBar.mjs +132 -130
  237. package/dist/ProgressBar.mjs.map +1 -1
  238. package/dist/ProgressCircle.cjs +32 -32
  239. package/dist/ProgressCircle.css +20 -20
  240. package/dist/ProgressCircle.css.map +1 -1
  241. package/dist/ProgressCircle.mjs +32 -32
  242. package/dist/Provider.cjs +11 -11
  243. package/dist/Provider.css +9 -11
  244. package/dist/Provider.css.map +1 -1
  245. package/dist/Provider.mjs +11 -11
  246. package/dist/Radio.cjs +101 -99
  247. package/dist/Radio.cjs.map +1 -1
  248. package/dist/Radio.css +101 -95
  249. package/dist/Radio.css.map +1 -1
  250. package/dist/Radio.mjs +101 -99
  251. package/dist/Radio.mjs.map +1 -1
  252. package/dist/RadioGroup.cjs +52 -52
  253. package/dist/RadioGroup.css +42 -42
  254. package/dist/RadioGroup.mjs +52 -52
  255. package/dist/SearchField.cjs +52 -52
  256. package/dist/SearchField.css +54 -54
  257. package/dist/SearchField.mjs +52 -52
  258. package/dist/SegmentedControl.cjs +123 -119
  259. package/dist/SegmentedControl.cjs.map +1 -1
  260. package/dist/SegmentedControl.css +110 -104
  261. package/dist/SegmentedControl.css.map +1 -1
  262. package/dist/SegmentedControl.mjs +123 -119
  263. package/dist/SegmentedControl.mjs.map +1 -1
  264. package/dist/Slider.cjs +251 -248
  265. package/dist/Slider.cjs.map +1 -1
  266. package/dist/Slider.css +166 -157
  267. package/dist/Slider.css.map +1 -1
  268. package/dist/Slider.mjs +251 -248
  269. package/dist/Slider.mjs.map +1 -1
  270. package/dist/StatusLight.cjs +74 -70
  271. package/dist/StatusLight.cjs.map +1 -1
  272. package/dist/StatusLight.css +70 -64
  273. package/dist/StatusLight.css.map +1 -1
  274. package/dist/StatusLight.mjs +74 -70
  275. package/dist/StatusLight.mjs.map +1 -1
  276. package/dist/Switch.cjs +110 -108
  277. package/dist/Switch.cjs.map +1 -1
  278. package/dist/Switch.css +86 -80
  279. package/dist/Switch.css.map +1 -1
  280. package/dist/Switch.mjs +110 -108
  281. package/dist/Switch.mjs.map +1 -1
  282. package/dist/TableView.cjs +334 -329
  283. package/dist/TableView.cjs.map +1 -1
  284. package/dist/TableView.css +193 -193
  285. package/dist/TableView.css.map +1 -1
  286. package/dist/TableView.mjs +334 -329
  287. package/dist/TableView.mjs.map +1 -1
  288. package/dist/Tabs.cjs +153 -149
  289. package/dist/Tabs.cjs.map +1 -1
  290. package/dist/Tabs.css +102 -96
  291. package/dist/Tabs.css.map +1 -1
  292. package/dist/Tabs.mjs +153 -149
  293. package/dist/Tabs.mjs.map +1 -1
  294. package/dist/TabsPicker.cjs +131 -129
  295. package/dist/TabsPicker.cjs.map +1 -1
  296. package/dist/TabsPicker.css +132 -126
  297. package/dist/TabsPicker.css.map +1 -1
  298. package/dist/TabsPicker.mjs +131 -129
  299. package/dist/TabsPicker.mjs.map +1 -1
  300. package/dist/TagGroup.cjs +197 -193
  301. package/dist/TagGroup.cjs.map +1 -1
  302. package/dist/TagGroup.css +164 -158
  303. package/dist/TagGroup.css.map +1 -1
  304. package/dist/TagGroup.mjs +197 -193
  305. package/dist/TagGroup.mjs.map +1 -1
  306. package/dist/TextField.cjs +67 -65
  307. package/dist/TextField.cjs.map +1 -1
  308. package/dist/TextField.css +71 -65
  309. package/dist/TextField.css.map +1 -1
  310. package/dist/TextField.mjs +67 -65
  311. package/dist/TextField.mjs.map +1 -1
  312. package/dist/Toast.cjs +138 -136
  313. package/dist/Toast.cjs.map +1 -1
  314. package/dist/Toast.css +117 -111
  315. package/dist/Toast.css.map +1 -1
  316. package/dist/Toast.mjs +138 -136
  317. package/dist/Toast.mjs.map +1 -1
  318. package/dist/ToggleButton.cjs +3 -3
  319. package/dist/ToggleButton.css +12 -14
  320. package/dist/ToggleButton.css.map +1 -1
  321. package/dist/ToggleButton.mjs +3 -3
  322. package/dist/Tooltip.cjs +99 -72
  323. package/dist/Tooltip.cjs.map +1 -1
  324. package/dist/Tooltip.css +87 -87
  325. package/dist/Tooltip.css.map +1 -1
  326. package/dist/Tooltip.mjs +99 -72
  327. package/dist/Tooltip.mjs.map +1 -1
  328. package/dist/TreeView.cjs +169 -157
  329. package/dist/TreeView.cjs.map +1 -1
  330. package/dist/TreeView.css +155 -153
  331. package/dist/TreeView.css.map +1 -1
  332. package/dist/TreeView.mjs +169 -157
  333. package/dist/TreeView.mjs.map +1 -1
  334. package/dist/types.d.ts +1 -1
  335. package/dist/types.d.ts.map +1 -1
  336. package/icons/Skeleton.cjs +3 -3
  337. package/icons/Skeleton.cjs.map +1 -1
  338. package/icons/Skeleton.css +8 -10
  339. package/icons/Skeleton.css.map +1 -1
  340. package/icons/Skeleton.mjs +3 -3
  341. package/icons/Skeleton.mjs.map +1 -1
  342. package/package.json +22 -22
  343. package/src/Card.tsx +3 -3
  344. package/src/Disclosure.tsx +3 -3
  345. package/src/TableView.tsx +1 -4
  346. package/src/style-utils.ts +0 -2
  347. package/style/__tests__/style-macro.test.js +323 -27
  348. package/style/dist/properties.cjs +4 -0
  349. package/style/dist/properties.cjs.map +1 -0
  350. package/style/dist/properties.mjs +6 -0
  351. package/style/dist/properties.mjs.map +1 -0
  352. package/style/dist/spectrum-theme.cjs +123 -124
  353. package/style/dist/spectrum-theme.cjs.map +1 -1
  354. package/style/dist/spectrum-theme.mjs +124 -125
  355. package/style/dist/spectrum-theme.mjs.map +1 -1
  356. package/style/dist/style-macro.cjs +244 -263
  357. package/style/dist/style-macro.cjs.map +1 -1
  358. package/style/dist/style-macro.mjs +240 -259
  359. package/style/dist/style-macro.mjs.map +1 -1
  360. package/style/dist/types.d.ts +84 -80
  361. package/style/dist/types.d.ts.map +1 -1
  362. package/style/properties.json +2422 -0
  363. package/style/spectrum-theme.ts +101 -110
  364. package/style/style-macro.ts +319 -220
  365. package/style/types.ts +8 -4
@@ -10,84 +10,115 @@
10
10
  * governing permissions and limitations under the License.
11
11
  */
12
12
 
13
- import type {Condition, CSSProperties, CSSValue, CustomValue, PropertyFunction, PropertyValueDefinition, PropertyValueMap, RenderProps, ShorthandProperty, StyleFunction, StyleValue, Theme, ThemeProperties, Value} from './types';
13
+ import type {Condition, CSSProperties, CSSValue, CustomValue, Property, PropertyValueDefinition, PropertyValueMap, RenderProps, ShorthandProperty, StyleFunction, StyleValue, Theme, ThemeProperties, Value} from './types';
14
+ import * as propertyInfo from './properties.json';
14
15
 
15
- let defaultArbitraryProperty = <T>(value: T, property: string) => ({[property]: value} as CSSProperties);
16
- export function createArbitraryProperty<T>(fn: (value: T, property: string) => CSSProperties = defaultArbitraryProperty): PropertyFunction<T> {
17
- return (value, property) => {
18
- let selector = Array.isArray(value) ? generateArbitraryValueSelector(value.map(v => JSON.stringify(v)).join('')) : generateArbitraryValueSelector(JSON.stringify(value));
19
- return {default: [fn(value, property), selector]};
20
- };
21
- }
16
+ export class ArbitraryProperty<T extends Value> implements Property<T> {
17
+ property: string;
18
+ toCSS: (value: T) => CSSValue;
22
19
 
23
- function recursiveValues(obj: object): string[] {
24
- return Object.values(obj).flatMap(v => typeof v === 'object' ? recursiveValues(v) : [v]);
20
+ constructor(property: string, toCSS?: (value: T) => CSSValue) {
21
+ this.property = property;
22
+ this.toCSS = toCSS || ((value) => String(value));
23
+ }
24
+
25
+ get cssProperties(): string[] {
26
+ return [this.property];
27
+ }
28
+
29
+ toCSSValue(value: T): PropertyValueDefinition<Value> {
30
+ return this.toCSS(value);
31
+ }
32
+
33
+ toCSSProperties(customProperty: string | null, value: PropertyValueDefinition<Value>): PropertyValueDefinition<[CSSProperties]> {
34
+ return mapConditionalValue(value, value => [{[customProperty || this.property]: String(value)}]);
35
+ }
25
36
  }
26
37
 
27
- export function createMappedProperty<T extends CSSValue>(fn: (value: string, property: string) => CSSProperties, values: PropertyValueMap<T> | string[]): PropertyFunction<T> {
28
- let valueMap = createValueLookup(Array.isArray(values) ? values : recursiveValues(values));
29
- return (value, property) => {
30
- let v = parseArbitraryValue(value);
31
- if (v) {
32
- return {default: [fn(v[0], property), v[1]]};
33
- }
38
+ export class MappedProperty<T extends CSSValue> extends ArbitraryProperty<T> implements Property<T> {
39
+ mapping: PropertyValueMap<T> | string[];
34
40
 
35
- let val = Array.isArray(values) ? value : values[String(value)];
36
- return mapConditionalValue(val, value => {
37
- return [fn(value, property), valueMap.get(value)!];
38
- });
39
- };
41
+ constructor(property: string, mapping: PropertyValueMap<T> | string[]) {
42
+ super(property);
43
+ this.mapping = mapping;
44
+ }
45
+
46
+ toCSSValue(value: T): PropertyValueDefinition<Value> {
47
+ if (Array.isArray(this.mapping)) {
48
+ if (!this.mapping.includes(String(value))) {
49
+ throw new Error('Invalid style value: ' + value);
50
+ }
51
+ return value;
52
+ } else {
53
+ let res = this.mapping[String(value)];
54
+ if (res == null) {
55
+ throw new Error('Invalid style value: ' + value);
56
+ }
57
+ return res;
58
+ }
59
+ }
40
60
  }
41
61
 
42
- export function createRenamedProperty<T extends CSSValue>(name: string, values: PropertyValueMap<T> | string[]): PropertyFunction<T> {
43
- return createMappedProperty((value, property) => ({[property.startsWith('--') ? property : name]: value}), values);
62
+ export type Color<C extends string> = C | `${string}/${number}`;
63
+ export class ColorProperty<C extends string> extends MappedProperty<C> implements Property<Color<C>> {
64
+ toCSSValue(value: Color<C>): PropertyValueDefinition<Value> {
65
+ let [color, opacity] = value.split('/');
66
+ return mapConditionalValue(this.mapping[color], value => {
67
+ return opacity ? `rgb(from ${value} r g b / ${opacity}%)` : value;
68
+ });
69
+ }
44
70
  }
45
71
 
46
- export function createSizingProperty<T extends CSSValue>(values: PropertyValueMap<T>, fn: (value: number) => string): PropertyFunction<T | (number & {})> {
47
- let valueMap = createValueLookup(Array.isArray(values) ? values : recursiveValues(values));
48
- return (value, property) => {
49
- let v = parseArbitraryValue(value);
50
- if (v) {
51
- return {default: [{[property]: v[0]}, v[1]]};
52
- }
72
+ export class SizingProperty<T extends CSSValue> extends MappedProperty<T> implements Property<T | number> {
73
+ numberToCSS: (value: number) => string;
53
74
 
54
- let val = values[String(value)];
55
- if (val != null) {
56
- return mapConditionalValue(val, value => {
57
- return [{[property]: value}, valueMap.get(value)!];
58
- });
59
- }
75
+ constructor(property: string, mapping: PropertyValueMap<T> | string[], numberToCSS: (value: number) => string) {
76
+ super(property, mapping);
77
+ this.numberToCSS = numberToCSS;
78
+ }
60
79
 
80
+ toCSSValue(value: T | number): PropertyValueDefinition<Value> {
61
81
  if (typeof value === 'number') {
62
- let cssValue = value === 0 ? '0px' : fn(value);
63
- return {default: [{[property]: cssValue}, generateName(value + valueMap.size)]};
82
+ return value === 0 ? '0px' : this.numberToCSS(value);
64
83
  }
65
84
 
66
- throw new Error('Invalid sizing value: ' + value);
67
- };
85
+ return super.toCSSValue(value);
86
+ }
68
87
  }
69
88
 
70
- export type Color<C extends string> = C | `${string}/${number}`;
71
- export function createColorProperty<C extends string>(colors: PropertyValueMap<C>, property?: keyof CSSProperties): PropertyFunction<Color<C>> {
72
- let valueMap = createValueLookup(Object.values(colors).flatMap((v: any) => typeof v === 'object' ? Object.values(v) : [v]));
73
- return (value: Color<C>, key: string) => {
74
- let v = parseArbitraryValue(value);
75
- if (v) {
76
- return {default: [{[property || key]: v[0]}, v[1]]};
89
+ export class ExpandedProperty<T extends Value> implements Property<T> {
90
+ cssProperties: string[];
91
+ mapping: PropertyValueMap<CSSValue> | null;
92
+ expand: (v: T | CSSValue) => CSSProperties;
93
+
94
+ constructor(properties: string[], expand: (v: T | CSSValue) => CSSProperties, mapping?: PropertyValueMap<CSSValue>) {
95
+ this.cssProperties = properties;
96
+ this.expand = expand;
97
+ this.mapping = mapping || null;
98
+ }
99
+
100
+ toCSSValue(value: T): PropertyValueDefinition<Value> {
101
+ if (!this.mapping) {
102
+ return value;
77
103
  }
78
104
 
79
- let [color, opacity] = value.split('/');
80
- let c = colors[color];
81
- return mapConditionalValue(c, value => {
82
- let css = opacity ? `rgb(from ${value} r g b / ${opacity}%)` : value;
83
- let selector = valueMap.get(value)! + (opacity ? opacity.replace('.', '-') : '');
84
- return [{[property || key]: css}, selector];
85
- });
86
- };
105
+ let res = this.mapping[String(value)];
106
+ if (res == null) {
107
+ throw new Error('Invalid style value: ' + value);
108
+ }
109
+ return res;
110
+ }
111
+
112
+ toCSSProperties(customProperty: string | null, value: PropertyValueDefinition<T>): PropertyValueDefinition<[CSSProperties]> {
113
+ if (customProperty) {
114
+ throw new Error('Style properties that expand into multiple CSS properties cannot be set as CSS variables.');
115
+ }
116
+ return mapConditionalValue(value, value => [this.expand(value)]);
117
+ }
87
118
  }
88
119
 
89
120
  function mapConditionalValue<T, U>(value: PropertyValueDefinition<T>, fn: (value: T) => U): PropertyValueDefinition<U> {
90
- if (typeof value === 'object') {
121
+ if (typeof value === 'object' && !Array.isArray(value)) {
91
122
  let res: PropertyValueDefinition<U> = {};
92
123
  for (let condition in value) {
93
124
  res[condition] = mapConditionalValue((value as any)[condition], fn);
@@ -114,23 +145,29 @@ function mapConditionalShorthand<T, C extends string, R extends RenderProps<stri
114
145
  }
115
146
  }
116
147
 
117
- function createValueLookup(values: Array<CSSValue>, atStart = false) {
118
- let map = new Map<CSSValue, string>();
119
- for (let value of values) {
120
- if (!map.has(value)) {
121
- map.set(value, generateName(map.size, atStart));
122
- }
148
+ export function parseArbitraryValue(value: Value): string | undefined {
149
+ if (typeof value === 'string' && value.startsWith('--')) {
150
+ return `var(${value})`;
151
+ } else if (typeof value === 'string' && value[0] === '[' && value[value.length - 1] === ']') {
152
+ return value.slice(1, -1);
123
153
  }
124
- return map;
125
154
  }
126
155
 
127
- export function parseArbitraryValue(value: string | number): string[] | undefined {
128
- if (typeof value === 'string' && value.startsWith('--')) {
129
- return [`var(${value})`, generateArbitraryValueSelector(value)];
130
- } else if (typeof value === 'string' && value[0] === '[' && value[value.length - 1] === ']') {
131
- let s = generateArbitraryValueSelector(value.slice(1, -1));
132
- return [value.slice(1, -1), s];
156
+ function shortCSSPropertyName(property: string) {
157
+ return propertyInfo.properties[property] ?? generateArbitraryValueSelector(property, true);
158
+ }
159
+
160
+ function classNamePrefix(property: string, cssProperty: string) {
161
+ let className = propertyInfo.properties[cssProperty];
162
+ if (className && property === '--' + className) {
163
+ return '-' + className + '_-';
133
164
  }
165
+
166
+ if (className && !property.startsWith('--')) {
167
+ return className;
168
+ }
169
+
170
+ return '-' + generateArbitraryValueSelector(property, true) + '-';
134
171
  }
135
172
 
136
173
  interface MacroContext {
@@ -138,13 +175,12 @@ interface MacroContext {
138
175
  }
139
176
 
140
177
  export function createTheme<T extends Theme>(theme: T): StyleFunction<ThemeProperties<T>, 'default' | Extract<keyof T['conditions'], string>> {
141
- let themePropertyMap = createValueLookup(Object.keys(theme.properties), true);
142
- let themeConditionMap = createValueLookup(Object.keys(theme.conditions), true);
143
- let propertyFunctions = new Map(Object.entries(theme.properties).map(([k, v]) => {
144
- if (typeof v === 'function') {
145
- return [k, v];
178
+ let properties = new Map<string, Property<any>>(Object.entries(theme.properties).map(([k, v]) => {
179
+ if (!Array.isArray(v) && v.cssProperties) {
180
+ return [k, v as Property<any>];
146
181
  }
147
- return [k, createMappedProperty((value, p) => ({[p]: value}), v) as PropertyFunction<Value>];
182
+
183
+ return [k, new MappedProperty(k, v as any)];
148
184
  }));
149
185
 
150
186
  let dependencies = new Set<string>();
@@ -160,13 +196,13 @@ export function createTheme<T extends Theme>(theme: T): StyleFunction<ThemePrope
160
196
  }
161
197
 
162
198
  // Generate rules for each property.
163
- let rules = new Map<string, Rule[]>();
199
+ let rules = new Map<string, Rule>();
164
200
  let values = new Map();
165
201
  dependencies.clear();
166
- let usedPriorities = 1;
202
+ let usedPriorities = 0;
167
203
  let setRules = (key: string, value: [number, Rule[]]) => {
168
204
  usedPriorities = Math.max(usedPriorities, value[0]);
169
- rules.set(key, value[1]);
205
+ rules.set(key, new GroupRule(value[1]));
170
206
  };
171
207
 
172
208
  hasConditions = false;
@@ -210,7 +246,8 @@ export function createTheme<T extends Theme>(theme: T): StyleFunction<ThemePrope
210
246
  if (!(dep in theme.properties)) {
211
247
  throw new Error(`Unknown dependency ${dep}`);
212
248
  }
213
- let name = `--${themePropertyMap.get(dep)}`;
249
+ let prop = properties.get(dep)!;
250
+ let name = `--${shortCSSPropertyName(prop.cssProperties[0])}`;
214
251
  // Could potentially use @property to prevent the var from inheriting in children.
215
252
  setRules(name, compileValue(name, dep, value));
216
253
  setRules(dep, compileValue(dep, dep, name));
@@ -218,10 +255,6 @@ export function createTheme<T extends Theme>(theme: T): StyleFunction<ThemePrope
218
255
  }
219
256
  dependencies.clear();
220
257
 
221
- // Prevent all global styles from leaking into this element.
222
- // The :not(#a#b) raises the specificity of the selector by 2 ids,
223
- // which can never occur on a real element, and will win over other
224
- // selectors such as class and element selectors.
225
258
  let css = '';
226
259
 
227
260
  // Declare layers for each priority ahead of time so the order is always correct.
@@ -241,28 +274,46 @@ export function createTheme<T extends Theme>(theme: T): StyleFunction<ThemePrope
241
274
  // Also generate a variable for each overridable property that overlaps with the style definition. If those are defined,
242
275
  // the defaults from the style definition are omitted.
243
276
  let allowedOverridesSet = new Set<string>();
244
- let js = 'let rules = " .";\n';
277
+ let js = 'let rules = " ";\n';
245
278
  if (allowedOverrides?.length) {
246
279
  for (let property of allowedOverrides) {
247
- if (themePropertyMap.has(property as string)) {
248
- allowedOverridesSet.add(themePropertyMap.get(property as string)!);
280
+ let shorthand = theme.shorthands[property];
281
+ let props = Array.isArray(shorthand) ? shorthand : [property];
282
+ for (let property of props) {
283
+ let prop = properties.get(property);
284
+ if (!prop) {
285
+ throw new Error(`Invalid property ${property} in allowedOverrides`);
286
+ }
287
+ for (let property of prop.cssProperties) {
288
+ allowedOverridesSet.add(property);
289
+ }
249
290
  }
250
291
  }
251
292
 
252
- js += `let matches = (overrides || '').match(/(?:^|\\s)(?:${[...allowedOverridesSet].join('|')})[^\\s]+/g) || [];\n`;
253
- js += 'rules += matches.join(\'\');\n';
254
293
  let loop = '';
255
294
  for (let property of rules.keys()) {
256
- let themeProperty = themePropertyMap.get(property);
257
- if (themeProperty && allowedOverridesSet.has(themeProperty)) {
258
- js += `let $${themeProperty} = false;\n`;
259
- loop += ` if (/^\\s*${themeProperty}/.test(p)) $${themeProperty} = true;\n`;
295
+ let prop = properties.get(property);
296
+ if (prop) {
297
+ for (let property of prop.cssProperties) {
298
+ if (property && allowedOverridesSet.has(property)) {
299
+ let selector = classNamePrefix(property, property);
300
+ let p = property.replace('--', '__');
301
+ js += `let ${p} = false;\n`;
302
+ loop += ` if (p[1] === ${JSON.stringify(selector)}) ${p} = true;\n`;
303
+ }
304
+ }
260
305
  }
261
306
  }
307
+
308
+ let regex = `/(?:^|\\s)(${[...allowedOverridesSet].map(p => classNamePrefix(p, p)).join('|')})[^\\s]+/g`;
262
309
  if (loop) {
310
+ js += `let matches = (overrides || '').matchAll(${regex});\n`;
263
311
  js += 'for (let p of matches) {\n';
264
312
  js += loop;
313
+ js += ' rules += p[0];\n';
265
314
  js += '}\n';
315
+ } else {
316
+ js += `rules += ((overrides || '').match(${regex}) || []).join('')\n`;
266
317
  }
267
318
  }
268
319
 
@@ -270,25 +321,13 @@ export function createTheme<T extends Theme>(theme: T): StyleFunction<ThemePrope
270
321
  let isStatic = !(hasConditions || allowedOverrides);
271
322
  let className = '';
272
323
  let rulesByLayer = new Map<string, string[]>();
273
- for (let [property, propertyRules] of rules) {
274
- if (isStatic) {
275
- className += getStaticClassName(propertyRules);
276
- } else {
277
- let themeProperty = themePropertyMap.get(property);
278
- let allowsOverrides = themeProperty && allowedOverridesSet.has(themeProperty);
279
- if (allowsOverrides) {
280
- // Omit the value if an override was passed in.
281
- js += `if (!$${themeProperty}) {\n`;
282
- }
283
- js += printJS(propertyRules) + '\n';
284
- if (allowsOverrides) {
285
- js += '}\n';
286
- }
287
- }
288
- for (let rule of propertyRules) {
289
- printRule(rule, rulesByLayer);
290
- }
324
+ let rootRule = new GroupRule([...rules.values()]);
325
+ if (isStatic) {
326
+ className += rootRule.getStaticClassName();
327
+ } else {
328
+ js += rootRule.toJS(allowedOverridesSet) + '\n';
291
329
  }
330
+ rootRule.toCSS(rulesByLayer);
292
331
 
293
332
  for (let [layer, rules] of rulesByLayer) {
294
333
  css += `@layer ${layerName(layer)} {\n`;
@@ -352,7 +391,7 @@ export function createTheme<T extends Theme>(theme: T): StyleFunction<ThemePrope
352
391
  // The @layer order in the generated CSS will ensure that it overrides classes applied by runtime conditions.
353
392
  let isCSSCondition = condition in theme.conditions || /^[@:]/.test(condition);
354
393
  if (!wasCSSCondition && isCSSCondition && rules.length) {
355
- rules = [{prelude: '', condition: '', layer: '', body: rules}];
394
+ rules = [new GroupRule(rules)];
356
395
  }
357
396
  wasCSSCondition = isCSSCondition;
358
397
 
@@ -390,56 +429,42 @@ export function createTheme<T extends Theme>(theme: T): StyleFunction<ThemePrope
390
429
 
391
430
  function compileCondition(conditions: Set<string>, condition: string, priority: number, rules: Rule[]): Rule[] {
392
431
  if (condition === 'default' || conditions.has(condition)) {
393
- return [{prelude: '', condition: '', layer: '', body: rules}];
432
+ return [new GroupRule(rules)];
394
433
  }
395
434
 
396
435
  if (condition in theme.conditions || /^[@:]/.test(condition)) {
397
436
  // Conditions starting with : are CSS pseudo classes. Nest them inside the parent rule.
398
437
  let prelude = theme.conditions[condition] || condition;
399
438
  if (prelude.startsWith(':')) {
400
- return [{
401
- prelude: '',
402
- layer: generateName(priority, true),
403
- body: rules.map(rule => nestRule(rule, prelude)),
404
- condition: ''
405
- }];
439
+ for (let rule of rules) {
440
+ rule.addPseudo(prelude);
441
+ }
442
+
443
+ return [new GroupRule(rules, generateName(priority, true))];
406
444
  }
407
445
 
408
446
  // Otherwise, wrap the rule in the condition (e.g. @media).
409
- return [{
410
- // Top level layer is based on the priority of the rule, not the condition.
411
- // Also group in a sub-layer based on the condition so that lightningcss can more effectively deduplicate rules.
412
- layer: `${generateName(priority, true)}.${themeConditionMap.get(condition) || generateArbitraryValueSelector(condition, true)}`,
413
- prelude,
414
- body: rules,
415
- condition: ''
416
- }];
447
+ // Top level layer is based on the priority of the rule, not the condition.
448
+ // Also group in a sub-layer based on the condition so that lightningcss can more effectively deduplicate rules.
449
+ let layer = `${generateName(priority, true)}.${propertyInfo.conditions[theme.conditions[condition] || condition] || generateArbitraryValueSelector(condition, true)}`;
450
+ return [new AtRule(rules, prelude, layer)];
417
451
  }
418
452
 
419
453
  hasConditions = true;
420
- return [{prelude: '', layer: '', condition, body: rules}];
454
+ return [new ConditionalRule(rules, condition)];
421
455
  }
422
456
 
423
457
  function compileRule(property: string, themeProperty: string, value: Value, priority: number, conditions: Set<string>, skipConditions: Set<string>): [number, Rule[]] {
424
- // Generate selector. This consists of three parts:
425
- // 1. Property. For custom properties we use a hash. For theme properties, we use the index within the theme.
426
- // 2. Conditions. This uses the index within the theme.
427
- // 3. Value. The index in the theme, or a hash for arbitrary values.
428
- let prelude = '.';
429
- if (property.startsWith('--')) {
430
- // Include both custom property name and theme property in case the same var is reused between multiple theme properties.
431
- prelude += generateArbitraryValueSelector(property, true) + '_' + themePropertyMap.get(themeProperty) + '-';
432
- } else {
433
- prelude += themePropertyMap.get(themeProperty);
434
- }
435
-
436
- let propertyFunction = propertyFunctions.get(themeProperty);
458
+ let propertyFunction = properties.get(themeProperty);
437
459
  if (propertyFunction) {
438
460
  // Expand value to conditional CSS values, and then to rules.
439
- let res = propertyFunction(value, property);
440
- return conditionalToRules(res, priority, conditions, skipConditions, (value, priority, conditions) => {
441
- let [obj, p] = value;
442
- let body = '';
461
+ let arbitrary = parseArbitraryValue(value);
462
+ let cssValue = arbitrary ? arbitrary : propertyFunction.toCSSValue(value);
463
+ let cssProperties = propertyFunction.toCSSProperties(property.startsWith('--') ? property : null, cssValue);
464
+
465
+ return conditionalToRules(cssProperties, priority, conditions, skipConditions, (value, priority, conditions) => {
466
+ let [obj] = value;
467
+ let rules: Rule[] = [];
443
468
  for (let key in obj) {
444
469
  let k = key as any;
445
470
  let value = obj[k];
@@ -449,26 +474,39 @@ export function createTheme<T extends Theme>(theme: T): StyleFunction<ThemePrope
449
474
  if (typeof value === 'string') {
450
475
  // Replace self() references with variables and track the dependencies.
451
476
  value = value.replace(/self\(([a-zA-Z]+)/g, (_, v) => {
477
+ let prop = properties.get(v);
478
+ if (!prop) {
479
+ throw new Error(`self(${v}) is invalid. ${v} is not a known property.`);
480
+ }
481
+ let cssProperties = prop.cssProperties;
482
+ if (cssProperties.length !== 1) {
483
+ throw new Error(`self(${v}) is not supported. ${v} expands to multiple CSS properties.`);
484
+ }
452
485
  dependencies.add(v);
453
- return `var(--${themePropertyMap.get(v)}`;
486
+ return `var(--${shortCSSPropertyName(cssProperties[0])}`;
454
487
  });
455
488
  }
456
- body += `${kebab(key)}: ${value};`;
457
- }
458
489
 
459
- let selector = prelude;
460
- if (conditions.size > 0) {
461
- for (let condition of conditions) {
462
- selector += themeConditionMap.get(condition) || generateArbitraryValueSelector(condition);
490
+ // Generate selector. This consists of three parts: property, conditions, value.
491
+ let cssProperty = key;
492
+ if (property.startsWith('--')) {
493
+ cssProperty = propertyFunction.cssProperties[0];
463
494
  }
464
- }
465
495
 
466
- let rules: Rule[] = [{
467
- condition: '',
468
- layer: '',
469
- prelude: selector + p,
470
- body
471
- }];
496
+ let className = classNamePrefix(key, cssProperty);
497
+ if (conditions.size > 0) {
498
+ for (let condition of conditions) {
499
+ className += propertyInfo.conditions[theme.conditions[condition] || condition] || generateArbitraryValueSelector(condition);
500
+ }
501
+ }
502
+
503
+ if (cssProperty !== key) {
504
+ className += shortCSSPropertyName(cssProperty);
505
+ }
506
+
507
+ className += propertyInfo.values[cssProperty]?.[String(value)] ?? generateArbitraryValueSelector(String(value));
508
+ rules.push(new StyleRule(className, key, String(value)));
509
+ }
472
510
 
473
511
  return [0, rules];
474
512
  });
@@ -478,24 +516,6 @@ export function createTheme<T extends Theme>(theme: T): StyleFunction<ThemePrope
478
516
  }
479
517
  }
480
518
 
481
- function nestRule(rule: Rule, prelude: string): Rule {
482
- if (Array.isArray(rule.body)) {
483
- return {
484
- prelude: rule.prelude,
485
- layer: rule.layer,
486
- body: rule.body.map(r => nestRule(r, prelude)),
487
- condition: rule.condition
488
- };
489
- } else {
490
- return {
491
- prelude: rule.prelude,
492
- layer: rule.layer,
493
- body: [{...rule, prelude: '&' + prelude}],
494
- condition: ''
495
- };
496
- }
497
- }
498
-
499
519
  function kebab(property: string) {
500
520
  if (property.startsWith('--')) {
501
521
  return property;
@@ -503,13 +523,6 @@ function kebab(property: string) {
503
523
  return property.replace(/([a-z])([A-Z])/g, (_, a, b) => `${a}-${b.toLowerCase()}`);
504
524
  }
505
525
 
506
- interface Rule {
507
- prelude: string,
508
- layer: string,
509
- condition: string,
510
- body: string | Rule[]
511
- }
512
-
513
526
  // Generate a class name from a number, e.g. index within the theme.
514
527
  // This maps to an alphabet containing lower case letters, upper case letters, and numbers.
515
528
  // For numbers larger than 62, an underscore is prepended.
@@ -535,11 +548,26 @@ function generateName(index: number, atStart = false): string {
535
548
 
536
549
  // For arbitrary values, we use a hash of the string to generate the class name.
537
550
  function generateArbitraryValueSelector(v: string, atStart = false) {
538
- let c = hash(v).toString(36);
551
+ let c = toBase62(hash(v));
539
552
  if (atStart && /^[0-9]/.test(c)) {
540
553
  c = `_${c}`;
541
554
  }
542
- return `-${c}`;
555
+ return c;
556
+ }
557
+
558
+ function toBase62(value: number) {
559
+ if (value === 0) {
560
+ return generateName(value);
561
+ }
562
+
563
+ let res = '';
564
+ while (value) {
565
+ let remainder = value % 62;
566
+ res += generateName(remainder);
567
+ value = Math.floor((value - remainder) / 62);
568
+ }
569
+
570
+ return res;
543
571
  }
544
572
 
545
573
  // djb2 hash function.
@@ -558,75 +586,146 @@ function layerName(name: string) {
558
586
  return `_.${name}`;
559
587
  }
560
588
 
561
- function printRule(rule: Rule, rulesByLayer: Map<string, string[]>, preludes: string[] = [], layer = 'a') {
562
- if (rule.prelude) {
563
- preludes.push(rule.prelude);
589
+ interface Rule {
590
+ addPseudo(prelude: string): void,
591
+ getStaticClassName(): string,
592
+ toCSS(rulesByLayer: Map<string, string[]>, preludes?: string[], layer?: string): void,
593
+ toJS(allowedOverridesSet: Set<string>, indent?: string): string
594
+ }
595
+
596
+ /** A CSS style rule. */
597
+ class StyleRule implements Rule {
598
+ className: string;
599
+ pseudos: string;
600
+ property: string;
601
+ value: string;
602
+
603
+ constructor(className: string, property: string, value: string) {
604
+ this.className = className;
605
+ this.pseudos = '';
606
+ this.property = property;
607
+ this.value = value;
564
608
  }
565
609
 
566
- if (typeof rule.body === 'string') {
610
+ addPseudo(prelude: string) {
611
+ this.pseudos += prelude;
612
+ }
613
+
614
+ getStaticClassName(): string {
615
+ return ' ' + this.className;
616
+ }
617
+
618
+ toCSS(rulesByLayer: Map<string, string[]>, preludes: string[] = [], layer = 'a') {
619
+ let prelude = `.${this.className}${this.pseudos}`;
620
+ preludes.push(prelude);
621
+
567
622
  // Nest rule in our stack of preludes (e.g. media queries/selectors).
568
623
  let content = ' ';
569
624
  preludes.forEach((p, i) => {
570
625
  content += `${p} {\n${' '.repeat((i + 2) * 2)}`;
571
626
  });
572
- content += rule.body + '\n';
627
+ content += `${kebab(this.property)}: ${this.value};\n`;
573
628
  preludes.map((_, i) => {
574
629
  content += `${' '.repeat((preludes.length - i) * 2)}}\n`;
575
630
  });
576
631
 
577
632
  // Group rule into the appropriate layer.
578
- let rules = rulesByLayer.get(rule.layer || layer);
633
+ let rules = rulesByLayer.get(layer);
579
634
  if (!rules) {
580
635
  rules = [];
581
- rulesByLayer.set(rule.layer || layer, rules);
636
+ rulesByLayer.set(layer, rules);
582
637
  }
583
638
  rules.push(content);
584
- } else {
585
- for (let b of rule.body) {
586
- printRule(b, rulesByLayer, preludes, rule.layer || layer);
587
- }
639
+ preludes.pop();
588
640
  }
589
641
 
590
- if (rule.prelude) {
591
- preludes.pop();
642
+ toJS(allowedOverridesSet: Set<string>, indent = ''): string {
643
+ let res = '';
644
+ if (allowedOverridesSet.has(this.property)) {
645
+ res += `${indent}if (!${this.property.replace('--', '__')}) `;
646
+ }
647
+ res += `${indent}rules += ' ${this.className}';`;
648
+ return res;
592
649
  }
593
650
  }
594
651
 
595
- function printJS(rules: Rule[], indent = ''): string {
596
- rules = rules.slice().reverse();
652
+ /** Base class for rules that contain other rules. */
653
+ class GroupRule implements Rule {
654
+ rules: Rule[];
655
+ layer: string | null;
597
656
 
598
- let conditional = rules.filter(rule => rule.condition).map((rule, i) => {
599
- return `${i > 0 ? ' else ' : ''}if (props.${rule.condition}) {\n${indent} ${printRuleChildren(rule, indent + ' ')}\n${indent}}`;
600
- });
657
+ constructor(rules: Rule[], layer?: string) {
658
+ this.rules = rules;
659
+ this.layer = layer ?? null;
660
+ }
601
661
 
602
- let elseCases = rules.filter(rule => !rule.condition).map(rule => printRuleChildren(rule, indent + ' '));
662
+ addPseudo(prelude: string) {
663
+ for (let rule of this.rules) {
664
+ rule.addPseudo(prelude);
665
+ }
666
+ }
603
667
 
604
- if (conditional.length && elseCases.length) {
605
- return `${conditional.join('')} else {\n${indent} ${elseCases.join('\n' + indent + ' ')}\n${indent}}`;
668
+ getStaticClassName(): string {
669
+ return this.rules.map(rule => rule.getStaticClassName()).join('');
606
670
  }
607
671
 
608
- if (conditional.length) {
609
- return conditional.join('');
672
+ toCSS(rulesByLayer: Map<string, string[]>, preludes?: string[], layer?: string) {
673
+ for (let rule of this.rules) {
674
+ rule.toCSS(rulesByLayer, preludes, this.layer || layer);
675
+ }
610
676
  }
611
677
 
612
- return elseCases.join('\n' + indent);
613
- }
678
+ toJS(allowedOverridesSet: Set<string>, indent = ''): string {
679
+ let rules = this.rules.slice();
680
+ let conditional = rules.filter(rule => rule instanceof ConditionalRule).reverse().map((rule, i) => {
681
+ return `${i > 0 ? ' else ' : ''}${rule.toJS(allowedOverridesSet, indent)}`;
682
+ });
614
683
 
615
- function printRuleChildren(rule: Rule, indent = '') {
616
- let res = '';
617
- if (rule.prelude.startsWith('.')) {
618
- res += `rules += ' ${rule.prelude.slice(1)}';`;
684
+ let elseCases = rules.filter(rule => !(rule instanceof ConditionalRule)).map(rule => rule.toJS(allowedOverridesSet, indent));
685
+ if (conditional.length && elseCases.length) {
686
+ return `${conditional.join('')} else {\n${indent} ${elseCases.join('\n' + indent + ' ')}\n${indent}}`;
687
+ }
688
+
689
+ if (conditional.length) {
690
+ return conditional.join('');
691
+ }
692
+
693
+ return elseCases.join('\n' + indent);
619
694
  }
695
+ }
696
+
697
+ /** A rule that applies conditionally in CSS (e.g. @media). */
698
+ class AtRule extends GroupRule {
699
+ prelude: string;
620
700
 
621
- if (Array.isArray(rule.body)) {
622
- res += printJS(rule.body, indent);
701
+ constructor(rules: Rule[], prelude: string, layer: string) {
702
+ super(rules, layer);
703
+ this.prelude = prelude;
623
704
  }
624
705
 
625
- return res;
706
+ toCSS(rulesByLayer: Map<string, string[]>, preludes: string[] = [], layer?: string): void {
707
+ preludes.push(this.prelude);
708
+ super.toCSS(rulesByLayer, preludes, layer);
709
+ preludes?.pop();
710
+ }
626
711
  }
627
712
 
628
- function getStaticClassName(rules: Rule[]): string {
629
- return rules.map(rule => (rule.prelude.startsWith('.') ? ' ' + rule.prelude.slice(1) : '') + (Array.isArray(rule.body) ? getStaticClassName(rule.body) : '')).join('');
713
+ /** A rule that applies conditionally at runtime. */
714
+ class ConditionalRule extends GroupRule {
715
+ condition: string;
716
+
717
+ constructor(rules: Rule[], condition: string) {
718
+ super(rules);
719
+ this.condition = condition;
720
+ }
721
+
722
+ getStaticClassName(): string {
723
+ throw new Error('Conditional rules cannot be compiled to a static class name. This is a bug.');
724
+ }
725
+
726
+ toJS(allowedOverridesSet: Set<string>, indent = ''): string {
727
+ return `${indent}if (props.${this.condition}) {\n${super.toJS(allowedOverridesSet, indent + ' ')}\n${indent}}`;
728
+ }
630
729
  }
631
730
 
632
731
  export function raw(this: MacroContext | void, css: string, layer = '_.a'): string {