@webstudio-is/css-engine 0.4.0 → 0.15.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.
@@ -81,8 +81,9 @@ class CssEngine {
81
81
  }
82
82
  addPlaintextRule(cssText) {
83
83
  const rule = __privateGet(this, _plainRules).get(cssText);
84
- if (rule !== void 0)
84
+ if (rule !== void 0) {
85
85
  return rule;
86
+ }
86
87
  __privateSet(this, _isDirty, true);
87
88
  return __privateGet(this, _plainRules).set(cssText, new import_rules.PlaintextRule(cssText));
88
89
  }
@@ -113,10 +114,11 @@ class CssEngine {
113
114
  for (const plaintextRule of __privateGet(this, _plainRules).values()) {
114
115
  css.push(plaintextRule.cssText);
115
116
  }
116
- for (const mediaRule of __privateGet(this, _mediaRules).values()) {
117
+ for (const mediaRule of import_rules.MediaRule.sort(__privateGet(this, _mediaRules).values())) {
117
118
  const { cssText } = mediaRule;
118
- if (cssText !== "")
119
+ if (cssText !== "") {
119
120
  css.push(cssText);
121
+ }
120
122
  }
121
123
  __privateSet(this, _cssText, css.join("\n"));
122
124
  return __privateGet(this, _cssText);
@@ -50,7 +50,7 @@ __export(rules_exports, {
50
50
  module.exports = __toCommonJS(rules_exports);
51
51
  var import_hyphenate_style_name = __toESM(require("hyphenate-style-name"), 1);
52
52
  var import_to_value = require("./to-value");
53
- var _styleMap, _isDirty, _string, _onChange, _options, _mediaType, _options2;
53
+ var _styleMap, _isDirty, _string, _onChange, _mediaType;
54
54
  class StylePropertyMap {
55
55
  constructor() {
56
56
  __privateAdd(this, _styleMap, /* @__PURE__ */ new Map());
@@ -76,8 +76,9 @@ class StylePropertyMap {
76
76
  }
77
77
  const block = [];
78
78
  for (const [property, value] of __privateGet(this, _styleMap)) {
79
- if (value === void 0)
79
+ if (value === void 0) {
80
80
  continue;
81
+ }
81
82
  block.push(`${(0, import_hyphenate_style_name.default)(property)}: ${(0, import_to_value.toValue)(value)}`);
82
83
  }
83
84
  __privateSet(this, _string, block.join("; "));
@@ -108,31 +109,39 @@ class StyleRule {
108
109
  _onChange = new WeakMap();
109
110
  class MediaRule {
110
111
  constructor(options = {}) {
111
- __privateAdd(this, _options, void 0);
112
112
  this.rules = [];
113
113
  __privateAdd(this, _mediaType, void 0);
114
- __privateSet(this, _options, options);
114
+ this.options = options;
115
115
  __privateSet(this, _mediaType, options.mediaType ?? "all");
116
116
  }
117
+ static sort(mediaRules) {
118
+ return Array.from(mediaRules).sort((ruleA, ruleB) => {
119
+ return (ruleA.options.minWidth ?? -Number.MAX_SAFE_INTEGER) - (ruleB.options.minWidth ?? -Number.MAX_SAFE_INTEGER);
120
+ });
121
+ }
117
122
  insertRule(rule) {
118
123
  this.rules.push(rule);
119
124
  return rule;
120
125
  }
121
126
  get cssText() {
122
- if (this.rules.length === 0)
127
+ if (this.rules.length === 0) {
123
128
  return "";
129
+ }
124
130
  const rules = [];
125
131
  for (const rule of this.rules) {
126
132
  rules.push(` ${rule.cssText}`);
127
133
  }
128
134
  let conditionText = "";
129
- const { minWidth, maxWidth } = __privateGet(this, _options);
130
- if (minWidth !== void 0)
135
+ const { minWidth, maxWidth } = this.options;
136
+ if (minWidth !== void 0) {
131
137
  conditionText = `min-width: ${minWidth}px`;
132
- if (maxWidth !== void 0)
138
+ }
139
+ if (maxWidth !== void 0) {
133
140
  conditionText = `max-width: ${maxWidth}px`;
134
- if (conditionText)
141
+ }
142
+ if (conditionText) {
135
143
  conditionText = `and (${conditionText}) `;
144
+ }
136
145
  return `@media ${__privateGet(this, _mediaType)} ${conditionText}{
137
146
  ${rules.join(
138
147
  "\n"
@@ -140,7 +149,6 @@ ${rules.join(
140
149
  }`;
141
150
  }
142
151
  }
143
- _options = new WeakMap();
144
152
  _mediaType = new WeakMap();
145
153
  class PlaintextRule {
146
154
  constructor(cssText) {
@@ -150,14 +158,12 @@ class PlaintextRule {
150
158
  }
151
159
  class FontFaceRule {
152
160
  constructor(options) {
153
- __privateAdd(this, _options2, void 0);
154
- __privateSet(this, _options2, options);
161
+ this.options = options;
155
162
  }
156
163
  get cssText() {
157
- const { fontFamily, fontStyle, fontWeight, fontDisplay, src } = __privateGet(this, _options2);
164
+ const { fontFamily, fontStyle, fontWeight, fontDisplay, src } = this.options;
158
165
  return `@font-face {
159
166
  font-family: ${fontFamily}; font-style: ${fontStyle}; font-weight: ${fontWeight}; font-display: ${fontDisplay}; src: ${src};
160
167
  }`;
161
168
  }
162
169
  }
163
- _options2 = new WeakMap();
@@ -25,9 +25,13 @@ var import_fonts = require("@webstudio-is/fonts");
25
25
  const defaultOptions = {
26
26
  withFallback: true
27
27
  };
28
+ const assertUnreachable = (_arg, errorMessage) => {
29
+ throw new Error(errorMessage);
30
+ };
28
31
  const toValue = (value, options = defaultOptions) => {
29
- if (value === void 0)
32
+ if (value === void 0) {
30
33
  return "";
34
+ }
31
35
  if (value.type === "unit") {
32
36
  return value.value + (value.unit === "number" ? "" : value.unit);
33
37
  }
@@ -50,5 +54,18 @@ const toValue = (value, options = defaultOptions) => {
50
54
  const fallbacksString = fallbacks.length > 0 ? `, ${fallbacks.join(", ")}` : "";
51
55
  return `var(--${value.value}${fallbacksString})`;
52
56
  }
53
- return value.value;
57
+ if (value.type === "keyword") {
58
+ return value.value;
59
+ }
60
+ if (value.type === "invalid") {
61
+ return value.value;
62
+ }
63
+ if (value.type === "unset") {
64
+ return value.value;
65
+ }
66
+ if (value.type === "rgb") {
67
+ return `rgba(${value.r}, ${value.g}, ${value.b}, ${value.alpha})`;
68
+ }
69
+ assertUnreachable(value, `Unknown value type`);
70
+ throw new Error("Unknown value type");
54
71
  };
@@ -63,8 +63,9 @@ class CssEngine {
63
63
  }
64
64
  addPlaintextRule(cssText) {
65
65
  const rule = __privateGet(this, _plainRules).get(cssText);
66
- if (rule !== void 0)
66
+ if (rule !== void 0) {
67
67
  return rule;
68
+ }
68
69
  __privateSet(this, _isDirty, true);
69
70
  return __privateGet(this, _plainRules).set(cssText, new PlaintextRule(cssText));
70
71
  }
@@ -95,10 +96,11 @@ class CssEngine {
95
96
  for (const plaintextRule of __privateGet(this, _plainRules).values()) {
96
97
  css.push(plaintextRule.cssText);
97
98
  }
98
- for (const mediaRule of __privateGet(this, _mediaRules).values()) {
99
+ for (const mediaRule of MediaRule.sort(__privateGet(this, _mediaRules).values())) {
99
100
  const { cssText } = mediaRule;
100
- if (cssText !== "")
101
+ if (cssText !== "") {
101
102
  css.push(cssText);
103
+ }
102
104
  }
103
105
  __privateSet(this, _cssText, css.join("\n"));
104
106
  return __privateGet(this, _cssText);
package/lib/core/rules.js CHANGED
@@ -16,7 +16,7 @@ var __privateSet = (obj, member, value, setter) => {
16
16
  setter ? setter.call(obj, value) : member.set(obj, value);
17
17
  return value;
18
18
  };
19
- var _styleMap, _isDirty, _string, _onChange, _options, _mediaType, _options2;
19
+ var _styleMap, _isDirty, _string, _onChange, _mediaType;
20
20
  import hyphenate from "hyphenate-style-name";
21
21
  import { toValue } from "./to-value";
22
22
  class StylePropertyMap {
@@ -44,8 +44,9 @@ class StylePropertyMap {
44
44
  }
45
45
  const block = [];
46
46
  for (const [property, value] of __privateGet(this, _styleMap)) {
47
- if (value === void 0)
47
+ if (value === void 0) {
48
48
  continue;
49
+ }
49
50
  block.push(`${hyphenate(property)}: ${toValue(value)}`);
50
51
  }
51
52
  __privateSet(this, _string, block.join("; "));
@@ -76,31 +77,39 @@ class StyleRule {
76
77
  _onChange = new WeakMap();
77
78
  class MediaRule {
78
79
  constructor(options = {}) {
79
- __privateAdd(this, _options, void 0);
80
80
  this.rules = [];
81
81
  __privateAdd(this, _mediaType, void 0);
82
- __privateSet(this, _options, options);
82
+ this.options = options;
83
83
  __privateSet(this, _mediaType, options.mediaType ?? "all");
84
84
  }
85
+ static sort(mediaRules) {
86
+ return Array.from(mediaRules).sort((ruleA, ruleB) => {
87
+ return (ruleA.options.minWidth ?? -Number.MAX_SAFE_INTEGER) - (ruleB.options.minWidth ?? -Number.MAX_SAFE_INTEGER);
88
+ });
89
+ }
85
90
  insertRule(rule) {
86
91
  this.rules.push(rule);
87
92
  return rule;
88
93
  }
89
94
  get cssText() {
90
- if (this.rules.length === 0)
95
+ if (this.rules.length === 0) {
91
96
  return "";
97
+ }
92
98
  const rules = [];
93
99
  for (const rule of this.rules) {
94
100
  rules.push(` ${rule.cssText}`);
95
101
  }
96
102
  let conditionText = "";
97
- const { minWidth, maxWidth } = __privateGet(this, _options);
98
- if (minWidth !== void 0)
103
+ const { minWidth, maxWidth } = this.options;
104
+ if (minWidth !== void 0) {
99
105
  conditionText = `min-width: ${minWidth}px`;
100
- if (maxWidth !== void 0)
106
+ }
107
+ if (maxWidth !== void 0) {
101
108
  conditionText = `max-width: ${maxWidth}px`;
102
- if (conditionText)
109
+ }
110
+ if (conditionText) {
103
111
  conditionText = `and (${conditionText}) `;
112
+ }
104
113
  return `@media ${__privateGet(this, _mediaType)} ${conditionText}{
105
114
  ${rules.join(
106
115
  "\n"
@@ -108,7 +117,6 @@ ${rules.join(
108
117
  }`;
109
118
  }
110
119
  }
111
- _options = new WeakMap();
112
120
  _mediaType = new WeakMap();
113
121
  class PlaintextRule {
114
122
  constructor(cssText) {
@@ -118,17 +126,15 @@ class PlaintextRule {
118
126
  }
119
127
  class FontFaceRule {
120
128
  constructor(options) {
121
- __privateAdd(this, _options2, void 0);
122
- __privateSet(this, _options2, options);
129
+ this.options = options;
123
130
  }
124
131
  get cssText() {
125
- const { fontFamily, fontStyle, fontWeight, fontDisplay, src } = __privateGet(this, _options2);
132
+ const { fontFamily, fontStyle, fontWeight, fontDisplay, src } = this.options;
126
133
  return `@font-face {
127
134
  font-family: ${fontFamily}; font-style: ${fontStyle}; font-weight: ${fontWeight}; font-display: ${fontDisplay}; src: ${src};
128
135
  }`;
129
136
  }
130
137
  }
131
- _options2 = new WeakMap();
132
138
  export {
133
139
  FontFaceRule,
134
140
  MediaRule,
@@ -2,9 +2,13 @@ import { DEFAULT_FONT_FALLBACK, SYSTEM_FONTS } from "@webstudio-is/fonts";
2
2
  const defaultOptions = {
3
3
  withFallback: true
4
4
  };
5
+ const assertUnreachable = (_arg, errorMessage) => {
6
+ throw new Error(errorMessage);
7
+ };
5
8
  const toValue = (value, options = defaultOptions) => {
6
- if (value === void 0)
9
+ if (value === void 0) {
7
10
  return "";
11
+ }
8
12
  if (value.type === "unit") {
9
13
  return value.value + (value.unit === "number" ? "" : value.unit);
10
14
  }
@@ -27,7 +31,20 @@ const toValue = (value, options = defaultOptions) => {
27
31
  const fallbacksString = fallbacks.length > 0 ? `, ${fallbacks.join(", ")}` : "";
28
32
  return `var(--${value.value}${fallbacksString})`;
29
33
  }
30
- return value.value;
34
+ if (value.type === "keyword") {
35
+ return value.value;
36
+ }
37
+ if (value.type === "invalid") {
38
+ return value.value;
39
+ }
40
+ if (value.type === "unset") {
41
+ return value.value;
42
+ }
43
+ if (value.type === "rgb") {
44
+ return `rgba(${value.r}, ${value.g}, ${value.b}, ${value.alpha})`;
45
+ }
46
+ assertUnreachable(value, `Unknown value type`);
47
+ throw new Error("Unknown value type");
31
48
  };
32
49
  export {
33
50
  toValue
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@webstudio-is/css-engine",
3
- "version": "0.4.0",
3
+ "version": "0.15.0",
4
4
  "description": "CSS Renderer for Webstudio",
5
5
  "author": "Webstudio <github@webstudio.is>",
6
6
  "homepage": "https://webstudio.is",
@@ -5,16 +5,25 @@ const style0 = {
5
5
  } as const;
6
6
 
7
7
  const mediaRuleOptions0 = { minWidth: 0 } as const;
8
- const mediaId = "0";
8
+ const mediaId0 = "0";
9
+
10
+ const style1 = {
11
+ display: { type: "keyword", value: "flex" },
12
+ } as const;
13
+
14
+ const mediaRuleOptions1 = { minWidth: 300 } as const;
15
+ const mediaId1 = "1";
9
16
 
10
17
  describe("CssEngine", () => {
11
18
  let engine: CssEngine;
12
19
 
13
- beforeEach(() => {
20
+ const reset = () => {
14
21
  engine = new CssEngine();
15
- });
22
+ };
16
23
 
17
- test("use default media rule when there is no matching one registrered", () => {
24
+ beforeEach(reset);
25
+
26
+ test("use default media rule when there is no matching one registered", () => {
18
27
  engine.addStyleRule(".c", {
19
28
  style: style0,
20
29
  breakpoint: "x",
@@ -35,10 +44,10 @@ describe("CssEngine", () => {
35
44
  }"
36
45
  `);
37
46
 
38
- engine.addMediaRule(mediaId, mediaRuleOptions0);
47
+ engine.addMediaRule(mediaId0, mediaRuleOptions0);
39
48
  engine.addStyleRule(".c1", {
40
49
  style: { color: { type: "keyword", value: "blue" } },
41
- breakpoint: mediaId,
50
+ breakpoint: mediaId0,
42
51
  });
43
52
  // Default media query should allways be the first to have the lowest source order specificity
44
53
  expect(engine.cssText).toMatchInlineSnapshot(`
@@ -52,8 +61,74 @@ describe("CssEngine", () => {
52
61
  `);
53
62
  });
54
63
 
64
+ test("sort media queries based on lower min-width", () => {
65
+ engine.addMediaRule(mediaId1, mediaRuleOptions1);
66
+ engine.addStyleRule(".c2", {
67
+ style: style1,
68
+ breakpoint: mediaId1,
69
+ });
70
+
71
+ engine.addMediaRule(mediaId0, mediaRuleOptions0);
72
+ engine.addStyleRule(".c1", {
73
+ style: style0,
74
+ breakpoint: mediaId0,
75
+ });
76
+
77
+ engine.addStyleRule(".c3", {
78
+ style: style0,
79
+ breakpoint: "x",
80
+ });
81
+
82
+ // Default media query should allways be the first to have the lowest source order specificity
83
+ expect(engine.cssText).toMatchInlineSnapshot(`
84
+ "@media all {
85
+ .c3 { display: block }
86
+ }
87
+ @media all and (min-width: 0px) {
88
+ .c1 { display: block }
89
+ }
90
+ @media all and (min-width: 300px) {
91
+ .c2 { display: flex }
92
+ }"
93
+ `);
94
+ });
95
+
96
+ test("keep the sort order when minWidth is not defined", () => {
97
+ engine.addStyleRule(".c0", {
98
+ style: style0,
99
+ breakpoint: "x",
100
+ });
101
+ engine.addStyleRule(".c1", {
102
+ style: style1,
103
+ breakpoint: "x",
104
+ });
105
+ expect(engine.cssText).toMatchInlineSnapshot(`
106
+ "@media all {
107
+ .c0 { display: block }
108
+ .c1 { display: flex }
109
+ }"
110
+ `);
111
+
112
+ reset();
113
+
114
+ engine.addStyleRule(".c1", {
115
+ style: style1,
116
+ breakpoint: "x",
117
+ });
118
+ engine.addStyleRule(".c0", {
119
+ style: style0,
120
+ breakpoint: "x",
121
+ });
122
+ expect(engine.cssText).toMatchInlineSnapshot(`
123
+ "@media all {
124
+ .c1 { display: flex }
125
+ .c0 { display: block }
126
+ }"
127
+ `);
128
+ });
129
+
55
130
  test("rule with multiple properties", () => {
56
- engine.addMediaRule(mediaId, mediaRuleOptions0);
131
+ engine.addMediaRule(mediaId0, mediaRuleOptions0);
57
132
  engine.addStyleRule(".c", {
58
133
  style: {
59
134
  ...style0,
@@ -69,7 +144,7 @@ describe("CssEngine", () => {
69
144
  });
70
145
 
71
146
  test("hyphenate property", () => {
72
- engine.addMediaRule(mediaId, mediaRuleOptions0);
147
+ engine.addMediaRule(mediaId0, mediaRuleOptions0);
73
148
  engine.addStyleRule(".c", {
74
149
  style: {
75
150
  backgroundColor: { type: "keyword", value: "red" },
@@ -84,7 +159,7 @@ describe("CssEngine", () => {
84
159
  });
85
160
 
86
161
  test("add rule", () => {
87
- engine.addMediaRule(mediaId, mediaRuleOptions0);
162
+ engine.addMediaRule(mediaId0, mediaRuleOptions0);
88
163
  const rule1 = engine.addStyleRule(".c", {
89
164
  style: {
90
165
  ...style0,
@@ -116,7 +191,7 @@ describe("CssEngine", () => {
116
191
  });
117
192
 
118
193
  test("update rule", () => {
119
- engine.addMediaRule(mediaId, mediaRuleOptions0);
194
+ engine.addMediaRule(mediaId0, mediaRuleOptions0);
120
195
  const rule = engine.addStyleRule(".c", {
121
196
  style: {
122
197
  ...style0,
@@ -147,7 +222,7 @@ describe("CssEngine", () => {
147
222
  });
148
223
 
149
224
  test("don't override media queries", () => {
150
- engine.addMediaRule(mediaId, mediaRuleOptions0);
225
+ engine.addMediaRule(mediaId0, mediaRuleOptions0);
151
226
  engine.addStyleRule(".c", {
152
227
  style: style0,
153
228
  breakpoint: "0",
@@ -157,7 +232,7 @@ describe("CssEngine", () => {
157
232
  .c { display: block }
158
233
  }"
159
234
  `);
160
- engine.addMediaRule(mediaId, mediaRuleOptions0);
235
+ engine.addMediaRule(mediaId0, mediaRuleOptions0);
161
236
  expect(engine.cssText).toMatchInlineSnapshot(`
162
237
  "@media all and (min-width: 0px) {
163
238
  .c { display: block }
@@ -47,7 +47,9 @@ export class CssEngine {
47
47
  }
48
48
  addPlaintextRule(cssText: string) {
49
49
  const rule = this.#plainRules.get(cssText);
50
- if (rule !== undefined) return rule;
50
+ if (rule !== undefined) {
51
+ return rule;
52
+ }
51
53
  this.#isDirty = true;
52
54
  return this.#plainRules.set(cssText, new PlaintextRule(cssText));
53
55
  }
@@ -80,9 +82,11 @@ export class CssEngine {
80
82
  for (const plaintextRule of this.#plainRules.values()) {
81
83
  css.push(plaintextRule.cssText);
82
84
  }
83
- for (const mediaRule of this.#mediaRules.values()) {
85
+ for (const mediaRule of MediaRule.sort(this.#mediaRules.values())) {
84
86
  const { cssText } = mediaRule;
85
- if (cssText !== "") css.push(cssText);
87
+ if (cssText !== "") {
88
+ css.push(cssText);
89
+ }
86
90
  }
87
91
  this.#cssText = css.join("\n");
88
92
  return this.#cssText;
package/src/core/rules.ts CHANGED
@@ -26,7 +26,9 @@ class StylePropertyMap {
26
26
  }
27
27
  const block: Array<string> = [];
28
28
  for (const [property, value] of this.#styleMap) {
29
- if (value === undefined) continue;
29
+ if (value === undefined) {
30
+ continue;
31
+ }
30
32
  block.push(`${hyphenate(property)}: ${toValue(value)}`);
31
33
  }
32
34
  this.#string = block.join("; ");
@@ -63,11 +65,22 @@ export type MediaRuleOptions = {
63
65
  };
64
66
 
65
67
  export class MediaRule {
66
- #options: MediaRuleOptions;
68
+ // Sort media rules by minWidth.
69
+ // Needed to ensure that more specific media rules are inserted after less specific ones.
70
+ // So that they get a higher specificity.
71
+ static sort(mediaRules: Iterable<MediaRule>) {
72
+ return Array.from(mediaRules).sort((ruleA, ruleB) => {
73
+ return (
74
+ (ruleA.options.minWidth ?? -Number.MAX_SAFE_INTEGER) -
75
+ (ruleB.options.minWidth ?? -Number.MAX_SAFE_INTEGER)
76
+ );
77
+ });
78
+ }
79
+ options: MediaRuleOptions;
67
80
  rules: Array<StyleRule | PlaintextRule> = [];
68
81
  #mediaType;
69
82
  constructor(options: MediaRuleOptions = {}) {
70
- this.#options = options;
83
+ this.options = options;
71
84
  this.#mediaType = options.mediaType ?? "all";
72
85
  }
73
86
  insertRule(rule: StyleRule | PlaintextRule) {
@@ -75,16 +88,24 @@ export class MediaRule {
75
88
  return rule;
76
89
  }
77
90
  get cssText() {
78
- if (this.rules.length === 0) return "";
91
+ if (this.rules.length === 0) {
92
+ return "";
93
+ }
79
94
  const rules = [];
80
95
  for (const rule of this.rules) {
81
96
  rules.push(` ${rule.cssText}`);
82
97
  }
83
98
  let conditionText = "";
84
- const { minWidth, maxWidth } = this.#options;
85
- if (minWidth !== undefined) conditionText = `min-width: ${minWidth}px`;
86
- if (maxWidth !== undefined) conditionText = `max-width: ${maxWidth}px`;
87
- if (conditionText) conditionText = `and (${conditionText}) `;
99
+ const { minWidth, maxWidth } = this.options;
100
+ if (minWidth !== undefined) {
101
+ conditionText = `min-width: ${minWidth}px`;
102
+ }
103
+ if (maxWidth !== undefined) {
104
+ conditionText = `max-width: ${maxWidth}px`;
105
+ }
106
+ if (conditionText) {
107
+ conditionText = `and (${conditionText}) `;
108
+ }
88
109
  return `@media ${this.#mediaType} ${conditionText}{\n${rules.join(
89
110
  "\n"
90
111
  )}\n}`;
@@ -108,13 +129,13 @@ export type FontFaceOptions = {
108
129
  };
109
130
 
110
131
  export class FontFaceRule {
111
- #options: FontFaceOptions;
132
+ options: FontFaceOptions;
112
133
  constructor(options: FontFaceOptions) {
113
- this.#options = options;
134
+ this.options = options;
114
135
  }
115
136
  get cssText() {
116
137
  const { fontFamily, fontStyle, fontWeight, fontDisplay, src } =
117
- this.#options;
138
+ this.options;
118
139
  return `@font-face {\n font-family: ${fontFamily}; font-style: ${fontStyle}; font-weight: ${fontWeight}; font-display: ${fontDisplay}; src: ${src};\n}`;
119
140
  }
120
141
  }
@@ -9,11 +9,18 @@ const defaultOptions = {
9
9
  withFallback: true,
10
10
  };
11
11
 
12
+ // exhaustive check, should never happen in runtime as ts would give error
13
+ const assertUnreachable = (_arg: never, errorMessage: string) => {
14
+ throw new Error(errorMessage);
15
+ };
16
+
12
17
  export const toValue = (
13
18
  value?: StyleValue,
14
19
  options: ToCssOptions = defaultOptions
15
20
  ): string => {
16
- if (value === undefined) return "";
21
+ if (value === undefined) {
22
+ return "";
23
+ }
17
24
  if (value.type === "unit") {
18
25
  return value.value + (value.unit === "number" ? "" : value.unit);
19
26
  }
@@ -37,5 +44,26 @@ export const toValue = (
37
44
  fallbacks.length > 0 ? `, ${fallbacks.join(", ")}` : "";
38
45
  return `var(--${value.value}${fallbacksString})`;
39
46
  }
40
- return value.value;
47
+
48
+ if (value.type === "keyword") {
49
+ return value.value;
50
+ }
51
+
52
+ if (value.type === "invalid") {
53
+ return value.value;
54
+ }
55
+
56
+ if (value.type === "unset") {
57
+ return value.value;
58
+ }
59
+
60
+ if (value.type === "rgb") {
61
+ return `rgba(${value.r}, ${value.g}, ${value.b}, ${value.alpha})`;
62
+ }
63
+
64
+ // Will give ts error in case of missing type
65
+ assertUnreachable(value, `Unknown value type`);
66
+
67
+ // Will never happen
68
+ throw new Error("Unknown value type");
41
69
  };