@webstudio-is/css-engine 0.95.0 → 0.97.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.
- package/lib/index.js +561 -3
- package/package.json +5 -5
- package/lib/__generated__/types.js +0 -1
- package/lib/core/compare-media.js +0 -16
- package/lib/core/compare-media.test.js +0 -63
- package/lib/core/create-css-engine.js +0 -5
- package/lib/core/css-engine.js +0 -105
- package/lib/core/css-engine.stories.js +0 -48
- package/lib/core/css-engine.test.js +0 -451
- package/lib/core/equal-media.js +0 -4
- package/lib/core/equal-media.test.js +0 -29
- package/lib/core/find-applicable-media.js +0 -11
- package/lib/core/find-applicable-media.test.js +0 -42
- package/lib/core/index.js +0 -9
- package/lib/core/match-media.js +0 -6
- package/lib/core/match-media.test.js +0 -22
- package/lib/core/rules.js +0 -162
- package/lib/core/style-element.js +0 -39
- package/lib/core/style-sheet.js +0 -14
- package/lib/core/to-property.js +0 -8
- package/lib/core/to-property.test.js +0 -11
- package/lib/core/to-value.js +0 -76
- package/lib/core/to-value.test.js +0 -123
- package/lib/schema.js +0 -98
package/lib/index.js
CHANGED
|
@@ -1,3 +1,561 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
1
|
+
// src/core/to-value.ts
|
|
2
|
+
import { captureError } from "@webstudio-is/error-utils";
|
|
3
|
+
import { DEFAULT_FONT_FALLBACK, SYSTEM_FONTS } from "@webstudio-is/fonts";
|
|
4
|
+
var fallbackTransform = (styleValue) => {
|
|
5
|
+
if (styleValue.type === "fontFamily") {
|
|
6
|
+
const firstFontFamily = styleValue.value[0];
|
|
7
|
+
const fallbacks = SYSTEM_FONTS.get(firstFontFamily);
|
|
8
|
+
const fontFamily = [...styleValue.value];
|
|
9
|
+
if (Array.isArray(fallbacks)) {
|
|
10
|
+
fontFamily.push(...fallbacks);
|
|
11
|
+
} else {
|
|
12
|
+
fontFamily.push(DEFAULT_FONT_FALLBACK);
|
|
13
|
+
}
|
|
14
|
+
return {
|
|
15
|
+
type: "fontFamily",
|
|
16
|
+
value: fontFamily
|
|
17
|
+
};
|
|
18
|
+
}
|
|
19
|
+
};
|
|
20
|
+
var toValue = (styleValue, transformValue) => {
|
|
21
|
+
if (styleValue === void 0) {
|
|
22
|
+
return "";
|
|
23
|
+
}
|
|
24
|
+
const transformedValue = transformValue?.(styleValue) ?? fallbackTransform(styleValue);
|
|
25
|
+
const value = transformedValue ?? styleValue;
|
|
26
|
+
if (value.type === "unit") {
|
|
27
|
+
return value.value + (value.unit === "number" ? "" : value.unit);
|
|
28
|
+
}
|
|
29
|
+
if (value.type === "fontFamily") {
|
|
30
|
+
return value.value.join(", ");
|
|
31
|
+
}
|
|
32
|
+
if (value.type === "var") {
|
|
33
|
+
const fallbacks = [];
|
|
34
|
+
for (const fallback of value.fallbacks) {
|
|
35
|
+
fallbacks.push(toValue(fallback, transformValue));
|
|
36
|
+
}
|
|
37
|
+
const fallbacksString = fallbacks.length > 0 ? `, ${fallbacks.join(", ")}` : "";
|
|
38
|
+
return `var(--${value.value}${fallbacksString})`;
|
|
39
|
+
}
|
|
40
|
+
if (value.type === "keyword") {
|
|
41
|
+
return value.value;
|
|
42
|
+
}
|
|
43
|
+
if (value.type === "invalid") {
|
|
44
|
+
return value.value;
|
|
45
|
+
}
|
|
46
|
+
if (value.type === "unset") {
|
|
47
|
+
return value.value;
|
|
48
|
+
}
|
|
49
|
+
if (value.type === "rgb") {
|
|
50
|
+
return `rgba(${value.r}, ${value.g}, ${value.b}, ${value.alpha})`;
|
|
51
|
+
}
|
|
52
|
+
if (value.type === "image") {
|
|
53
|
+
if (value.hidden || value.value.type !== "url") {
|
|
54
|
+
return "none";
|
|
55
|
+
}
|
|
56
|
+
return `url(${value.value.url})`;
|
|
57
|
+
}
|
|
58
|
+
if (value.type === "unparsed") {
|
|
59
|
+
if (value.hidden) {
|
|
60
|
+
return "none";
|
|
61
|
+
}
|
|
62
|
+
return value.value;
|
|
63
|
+
}
|
|
64
|
+
if (value.type === "layers") {
|
|
65
|
+
const valueString = value.value.filter(
|
|
66
|
+
(layer) => "hidden" in layer === false || "hidden" in layer && layer.hidden === false
|
|
67
|
+
).map((layer) => {
|
|
68
|
+
return toValue(layer, transformValue);
|
|
69
|
+
}).join(", ");
|
|
70
|
+
return valueString === "" ? "none" : valueString;
|
|
71
|
+
}
|
|
72
|
+
if (value.type === "tuple") {
|
|
73
|
+
return value.value.map((value2) => toValue(value2, transformValue)).join(" ");
|
|
74
|
+
}
|
|
75
|
+
return captureError(new Error("Unknown value type"), value);
|
|
76
|
+
};
|
|
77
|
+
|
|
78
|
+
// src/core/to-property.ts
|
|
79
|
+
import hyphenate from "hyphenate-style-name";
|
|
80
|
+
var toProperty = (property) => {
|
|
81
|
+
if (property === "backgroundClip") {
|
|
82
|
+
return "-webkit-background-clip";
|
|
83
|
+
}
|
|
84
|
+
return hyphenate(property);
|
|
85
|
+
};
|
|
86
|
+
|
|
87
|
+
// src/core/rules.ts
|
|
88
|
+
var StylePropertyMap = class {
|
|
89
|
+
#styleMap = /* @__PURE__ */ new Map();
|
|
90
|
+
#isDirty = false;
|
|
91
|
+
#string = "";
|
|
92
|
+
#indent = 0;
|
|
93
|
+
#transformValue;
|
|
94
|
+
onChange;
|
|
95
|
+
constructor(transformValue) {
|
|
96
|
+
this.#transformValue = transformValue;
|
|
97
|
+
}
|
|
98
|
+
setTransformer(transformValue) {
|
|
99
|
+
this.#transformValue = transformValue;
|
|
100
|
+
}
|
|
101
|
+
set(property, value) {
|
|
102
|
+
this.#styleMap.set(property, value);
|
|
103
|
+
this.#isDirty = true;
|
|
104
|
+
this.onChange?.();
|
|
105
|
+
}
|
|
106
|
+
has(property) {
|
|
107
|
+
return this.#styleMap.has(property);
|
|
108
|
+
}
|
|
109
|
+
get size() {
|
|
110
|
+
return this.#styleMap.size;
|
|
111
|
+
}
|
|
112
|
+
keys() {
|
|
113
|
+
return this.#styleMap.keys();
|
|
114
|
+
}
|
|
115
|
+
delete(property) {
|
|
116
|
+
this.#styleMap.delete(property);
|
|
117
|
+
this.#isDirty = true;
|
|
118
|
+
this.onChange?.();
|
|
119
|
+
}
|
|
120
|
+
clear() {
|
|
121
|
+
this.#styleMap.clear();
|
|
122
|
+
this.#isDirty = true;
|
|
123
|
+
this.onChange?.();
|
|
124
|
+
}
|
|
125
|
+
toString({ indent = 0 } = {}) {
|
|
126
|
+
if (this.#isDirty === false && indent === this.#indent) {
|
|
127
|
+
return this.#string;
|
|
128
|
+
}
|
|
129
|
+
this.#indent = indent;
|
|
130
|
+
const block = [];
|
|
131
|
+
const spaces = " ".repeat(indent);
|
|
132
|
+
for (const [property, value] of this.#styleMap) {
|
|
133
|
+
if (value === void 0) {
|
|
134
|
+
continue;
|
|
135
|
+
}
|
|
136
|
+
block.push(
|
|
137
|
+
`${spaces}${toProperty(property)}: ${toValue(
|
|
138
|
+
value,
|
|
139
|
+
this.#transformValue
|
|
140
|
+
)}`
|
|
141
|
+
);
|
|
142
|
+
}
|
|
143
|
+
this.#string = block.join(";\n");
|
|
144
|
+
this.#isDirty = false;
|
|
145
|
+
return this.#string;
|
|
146
|
+
}
|
|
147
|
+
};
|
|
148
|
+
var StyleRule = class {
|
|
149
|
+
styleMap;
|
|
150
|
+
selectorText;
|
|
151
|
+
onChange;
|
|
152
|
+
constructor(selectorText, style, transformValue) {
|
|
153
|
+
this.styleMap = new StylePropertyMap(transformValue);
|
|
154
|
+
this.selectorText = selectorText;
|
|
155
|
+
let property;
|
|
156
|
+
for (property in style) {
|
|
157
|
+
this.styleMap.set(property, style[property]);
|
|
158
|
+
}
|
|
159
|
+
this.styleMap.onChange = this.#onChange;
|
|
160
|
+
}
|
|
161
|
+
#onChange = () => {
|
|
162
|
+
this.onChange?.();
|
|
163
|
+
};
|
|
164
|
+
get cssText() {
|
|
165
|
+
return this.toString();
|
|
166
|
+
}
|
|
167
|
+
toString(options = { indent: 0 }) {
|
|
168
|
+
const spaces = " ".repeat(options.indent);
|
|
169
|
+
return `${spaces}${this.selectorText} {
|
|
170
|
+
${this.styleMap.toString({
|
|
171
|
+
indent: options.indent + 2
|
|
172
|
+
})}
|
|
173
|
+
${spaces}}`;
|
|
174
|
+
}
|
|
175
|
+
};
|
|
176
|
+
var MediaRule = class {
|
|
177
|
+
options;
|
|
178
|
+
rules = [];
|
|
179
|
+
#mediaType;
|
|
180
|
+
constructor(options = {}) {
|
|
181
|
+
this.options = options;
|
|
182
|
+
this.#mediaType = options.mediaType ?? "all";
|
|
183
|
+
}
|
|
184
|
+
insertRule(rule) {
|
|
185
|
+
this.rules.push(rule);
|
|
186
|
+
return rule;
|
|
187
|
+
}
|
|
188
|
+
get cssText() {
|
|
189
|
+
return this.toString();
|
|
190
|
+
}
|
|
191
|
+
toString() {
|
|
192
|
+
if (this.rules.length === 0) {
|
|
193
|
+
return "";
|
|
194
|
+
}
|
|
195
|
+
const rules = [];
|
|
196
|
+
for (const rule of this.rules) {
|
|
197
|
+
rules.push(rule.toString({ indent: 2 }));
|
|
198
|
+
}
|
|
199
|
+
let conditionText = "";
|
|
200
|
+
const { minWidth, maxWidth } = this.options;
|
|
201
|
+
if (minWidth !== void 0) {
|
|
202
|
+
conditionText = ` and (min-width: ${minWidth}px)`;
|
|
203
|
+
}
|
|
204
|
+
if (maxWidth !== void 0) {
|
|
205
|
+
conditionText += ` and (max-width: ${maxWidth}px)`;
|
|
206
|
+
}
|
|
207
|
+
return `@media ${this.#mediaType}${conditionText} {
|
|
208
|
+
${rules.join(
|
|
209
|
+
"\n"
|
|
210
|
+
)}
|
|
211
|
+
}`;
|
|
212
|
+
}
|
|
213
|
+
};
|
|
214
|
+
var PlaintextRule = class {
|
|
215
|
+
cssText;
|
|
216
|
+
styleMap = new StylePropertyMap();
|
|
217
|
+
constructor(cssText) {
|
|
218
|
+
this.cssText = cssText;
|
|
219
|
+
}
|
|
220
|
+
toString() {
|
|
221
|
+
return this.cssText;
|
|
222
|
+
}
|
|
223
|
+
};
|
|
224
|
+
var FontFaceRule = class {
|
|
225
|
+
options;
|
|
226
|
+
constructor(options) {
|
|
227
|
+
this.options = options;
|
|
228
|
+
}
|
|
229
|
+
get cssText() {
|
|
230
|
+
return this.toString();
|
|
231
|
+
}
|
|
232
|
+
toString() {
|
|
233
|
+
const decls = [];
|
|
234
|
+
const { fontFamily, fontStyle, fontWeight, fontDisplay, src } = this.options;
|
|
235
|
+
decls.push(
|
|
236
|
+
`font-family: ${/\s/.test(fontFamily) ? `"${fontFamily}"` : fontFamily}`
|
|
237
|
+
);
|
|
238
|
+
decls.push(`font-style: ${fontStyle}`);
|
|
239
|
+
decls.push(`font-weight: ${fontWeight}`);
|
|
240
|
+
decls.push(`font-display: ${fontDisplay}`);
|
|
241
|
+
decls.push(`src: ${src}`);
|
|
242
|
+
return `@font-face {
|
|
243
|
+
${decls.join("; ")};
|
|
244
|
+
}`;
|
|
245
|
+
}
|
|
246
|
+
};
|
|
247
|
+
|
|
248
|
+
// src/core/compare-media.ts
|
|
249
|
+
var compareMedia = (optionA, optionB) => {
|
|
250
|
+
if (optionA.minWidth === void 0 && optionA.maxWidth === void 0) {
|
|
251
|
+
return -1;
|
|
252
|
+
}
|
|
253
|
+
if (optionB.minWidth === void 0 && optionB.maxWidth === void 0) {
|
|
254
|
+
return 1;
|
|
255
|
+
}
|
|
256
|
+
if (optionA.minWidth !== void 0 && optionB.minWidth !== void 0) {
|
|
257
|
+
return optionA.minWidth - optionB.minWidth;
|
|
258
|
+
}
|
|
259
|
+
if (optionA.maxWidth !== void 0 && optionB.maxWidth !== void 0) {
|
|
260
|
+
return optionB.maxWidth - optionA.maxWidth;
|
|
261
|
+
}
|
|
262
|
+
return "minWidth" in optionA ? 1 : -1;
|
|
263
|
+
};
|
|
264
|
+
|
|
265
|
+
// src/core/style-element.ts
|
|
266
|
+
var StyleElement = class {
|
|
267
|
+
#element;
|
|
268
|
+
#name;
|
|
269
|
+
constructor(name = "") {
|
|
270
|
+
this.#name = name;
|
|
271
|
+
}
|
|
272
|
+
get isMounted() {
|
|
273
|
+
return this.#element?.parentElement != null;
|
|
274
|
+
}
|
|
275
|
+
mount() {
|
|
276
|
+
if (this.isMounted === false) {
|
|
277
|
+
this.#element = document.createElement("style");
|
|
278
|
+
this.#element.setAttribute("data-webstudio", this.#name);
|
|
279
|
+
document.head.appendChild(this.#element);
|
|
280
|
+
}
|
|
281
|
+
}
|
|
282
|
+
unmount() {
|
|
283
|
+
if (this.isMounted) {
|
|
284
|
+
this.#element?.parentElement?.removeChild(this.#element);
|
|
285
|
+
this.#element = void 0;
|
|
286
|
+
}
|
|
287
|
+
}
|
|
288
|
+
render(cssText) {
|
|
289
|
+
if (this.#element) {
|
|
290
|
+
this.#element.textContent = cssText;
|
|
291
|
+
}
|
|
292
|
+
}
|
|
293
|
+
setAttribute(name, value) {
|
|
294
|
+
if (this.#element) {
|
|
295
|
+
this.#element.setAttribute(name, value);
|
|
296
|
+
}
|
|
297
|
+
}
|
|
298
|
+
getAttribute(name) {
|
|
299
|
+
if (this.#element) {
|
|
300
|
+
return this.#element.getAttribute(name);
|
|
301
|
+
}
|
|
302
|
+
}
|
|
303
|
+
};
|
|
304
|
+
|
|
305
|
+
// src/core/style-sheet.ts
|
|
306
|
+
var StyleSheet = class {
|
|
307
|
+
#cssText = "";
|
|
308
|
+
#element;
|
|
309
|
+
constructor(element) {
|
|
310
|
+
this.#element = element;
|
|
311
|
+
}
|
|
312
|
+
replaceSync(cssText) {
|
|
313
|
+
if (cssText !== this.#cssText) {
|
|
314
|
+
this.#cssText = cssText;
|
|
315
|
+
this.#element.render(cssText);
|
|
316
|
+
}
|
|
317
|
+
}
|
|
318
|
+
};
|
|
319
|
+
|
|
320
|
+
// src/core/css-engine.ts
|
|
321
|
+
var defaultMediaRuleId = "__default-media-rule__";
|
|
322
|
+
var CssEngine = class {
|
|
323
|
+
#element;
|
|
324
|
+
#mediaRules = /* @__PURE__ */ new Map();
|
|
325
|
+
#plainRules = /* @__PURE__ */ new Map();
|
|
326
|
+
#fontFaceRules = [];
|
|
327
|
+
#sheet;
|
|
328
|
+
#isDirty = false;
|
|
329
|
+
#cssText = "";
|
|
330
|
+
constructor({ name }) {
|
|
331
|
+
this.#element = new StyleElement(name);
|
|
332
|
+
this.#sheet = new StyleSheet(this.#element);
|
|
333
|
+
}
|
|
334
|
+
addMediaRule(id, options) {
|
|
335
|
+
let mediaRule = this.#mediaRules.get(id);
|
|
336
|
+
if (mediaRule === void 0) {
|
|
337
|
+
mediaRule = new MediaRule(options);
|
|
338
|
+
this.#mediaRules.set(id, mediaRule);
|
|
339
|
+
this.#isDirty = true;
|
|
340
|
+
return mediaRule;
|
|
341
|
+
}
|
|
342
|
+
if (options) {
|
|
343
|
+
mediaRule.options = options;
|
|
344
|
+
this.#isDirty = true;
|
|
345
|
+
}
|
|
346
|
+
return mediaRule;
|
|
347
|
+
}
|
|
348
|
+
addStyleRule(selectorText, rule, transformValue) {
|
|
349
|
+
const mediaRule = this.addMediaRule(rule.breakpoint || defaultMediaRuleId);
|
|
350
|
+
this.#isDirty = true;
|
|
351
|
+
const styleRule = new StyleRule(selectorText, rule.style, transformValue);
|
|
352
|
+
styleRule.onChange = this.#onChangeRule;
|
|
353
|
+
if (mediaRule === void 0) {
|
|
354
|
+
throw new Error("No media rule found");
|
|
355
|
+
}
|
|
356
|
+
mediaRule.insertRule(styleRule);
|
|
357
|
+
return styleRule;
|
|
358
|
+
}
|
|
359
|
+
addPlaintextRule(cssText) {
|
|
360
|
+
const rule = this.#plainRules.get(cssText);
|
|
361
|
+
if (rule !== void 0) {
|
|
362
|
+
return rule;
|
|
363
|
+
}
|
|
364
|
+
this.#isDirty = true;
|
|
365
|
+
return this.#plainRules.set(cssText, new PlaintextRule(cssText));
|
|
366
|
+
}
|
|
367
|
+
addFontFaceRule(options) {
|
|
368
|
+
this.#isDirty = true;
|
|
369
|
+
return this.#fontFaceRules.push(new FontFaceRule(options));
|
|
370
|
+
}
|
|
371
|
+
clear() {
|
|
372
|
+
this.#mediaRules.clear();
|
|
373
|
+
this.#plainRules.clear();
|
|
374
|
+
this.#fontFaceRules = [];
|
|
375
|
+
this.#isDirty = true;
|
|
376
|
+
}
|
|
377
|
+
render() {
|
|
378
|
+
this.#element.mount();
|
|
379
|
+
this.#sheet.replaceSync(this.cssText);
|
|
380
|
+
}
|
|
381
|
+
unmount() {
|
|
382
|
+
this.#element.unmount();
|
|
383
|
+
}
|
|
384
|
+
setAttribute(name, value) {
|
|
385
|
+
this.#element.setAttribute(name, value);
|
|
386
|
+
}
|
|
387
|
+
getAttribute(name) {
|
|
388
|
+
return this.#element.getAttribute(name);
|
|
389
|
+
}
|
|
390
|
+
get cssText() {
|
|
391
|
+
if (this.#isDirty === false) {
|
|
392
|
+
return this.#cssText;
|
|
393
|
+
}
|
|
394
|
+
this.#isDirty = false;
|
|
395
|
+
const css = [];
|
|
396
|
+
css.push(...this.#fontFaceRules.map((rule) => rule.cssText));
|
|
397
|
+
for (const plaintextRule of this.#plainRules.values()) {
|
|
398
|
+
css.push(plaintextRule.cssText);
|
|
399
|
+
}
|
|
400
|
+
const sortedMediaRules = Array.from(this.#mediaRules.values()).sort(
|
|
401
|
+
(ruleA, ruleB) => compareMedia(ruleA.options, ruleB.options)
|
|
402
|
+
);
|
|
403
|
+
for (const mediaRule of sortedMediaRules) {
|
|
404
|
+
const { cssText } = mediaRule;
|
|
405
|
+
if (cssText !== "") {
|
|
406
|
+
css.push(cssText);
|
|
407
|
+
}
|
|
408
|
+
}
|
|
409
|
+
this.#cssText = css.join("\n");
|
|
410
|
+
return this.#cssText;
|
|
411
|
+
}
|
|
412
|
+
#onChangeRule = () => {
|
|
413
|
+
this.#isDirty = true;
|
|
414
|
+
};
|
|
415
|
+
};
|
|
416
|
+
|
|
417
|
+
// src/core/create-css-engine.ts
|
|
418
|
+
var createCssEngine = (options = {}) => {
|
|
419
|
+
return new CssEngine(options);
|
|
420
|
+
};
|
|
421
|
+
|
|
422
|
+
// src/core/match-media.ts
|
|
423
|
+
var matchMedia = (options, width) => {
|
|
424
|
+
const minWidth = options.minWidth ?? Number.MIN_SAFE_INTEGER;
|
|
425
|
+
const maxWidth = options.maxWidth ?? Number.MAX_SAFE_INTEGER;
|
|
426
|
+
return width >= minWidth && width <= maxWidth;
|
|
427
|
+
};
|
|
428
|
+
|
|
429
|
+
// src/core/equal-media.ts
|
|
430
|
+
var equalMedia = (left, right) => {
|
|
431
|
+
return left.minWidth === right.minWidth && left.maxWidth === right.maxWidth;
|
|
432
|
+
};
|
|
433
|
+
|
|
434
|
+
// src/core/find-applicable-media.ts
|
|
435
|
+
var findApplicableMedia = (media, width) => {
|
|
436
|
+
const sortedMedia = [...media].sort(compareMedia).reverse();
|
|
437
|
+
for (const options of sortedMedia) {
|
|
438
|
+
if (matchMedia(options, width)) {
|
|
439
|
+
return options;
|
|
440
|
+
}
|
|
441
|
+
}
|
|
442
|
+
};
|
|
443
|
+
|
|
444
|
+
// src/schema.ts
|
|
445
|
+
import { z } from "zod";
|
|
446
|
+
var Unit = z.string();
|
|
447
|
+
var UnitValue = z.object({
|
|
448
|
+
type: z.literal("unit"),
|
|
449
|
+
unit: Unit,
|
|
450
|
+
value: z.number()
|
|
451
|
+
});
|
|
452
|
+
var KeywordValue = z.object({
|
|
453
|
+
type: z.literal("keyword"),
|
|
454
|
+
// @todo use exact type
|
|
455
|
+
value: z.string()
|
|
456
|
+
});
|
|
457
|
+
var UnparsedValue = z.object({
|
|
458
|
+
type: z.literal("unparsed"),
|
|
459
|
+
value: z.string(),
|
|
460
|
+
// For the builder we want to be able to hide background-image
|
|
461
|
+
hidden: z.boolean().optional()
|
|
462
|
+
});
|
|
463
|
+
var FontFamilyValue = z.object({
|
|
464
|
+
type: z.literal("fontFamily"),
|
|
465
|
+
value: z.array(z.string())
|
|
466
|
+
});
|
|
467
|
+
var RgbValue = z.object({
|
|
468
|
+
type: z.literal("rgb"),
|
|
469
|
+
r: z.number(),
|
|
470
|
+
g: z.number(),
|
|
471
|
+
b: z.number(),
|
|
472
|
+
alpha: z.number()
|
|
473
|
+
});
|
|
474
|
+
var ImageValue = z.object({
|
|
475
|
+
type: z.literal("image"),
|
|
476
|
+
value: z.union([
|
|
477
|
+
z.object({ type: z.literal("asset"), value: z.string() }),
|
|
478
|
+
// url is not stored in db and only used by css-engine transformValue
|
|
479
|
+
// to prepare image value for rendering
|
|
480
|
+
z.object({ type: z.literal("url"), url: z.string() })
|
|
481
|
+
]),
|
|
482
|
+
// For the builder we want to be able to hide images
|
|
483
|
+
hidden: z.boolean().optional()
|
|
484
|
+
});
|
|
485
|
+
var InvalidValue = z.object({
|
|
486
|
+
type: z.literal("invalid"),
|
|
487
|
+
value: z.string()
|
|
488
|
+
});
|
|
489
|
+
var UnsetValue = z.object({
|
|
490
|
+
type: z.literal("unset"),
|
|
491
|
+
value: z.literal("")
|
|
492
|
+
});
|
|
493
|
+
var TupleValueItem = z.union([
|
|
494
|
+
UnitValue,
|
|
495
|
+
KeywordValue,
|
|
496
|
+
UnparsedValue,
|
|
497
|
+
RgbValue
|
|
498
|
+
]);
|
|
499
|
+
var TupleValue = z.object({
|
|
500
|
+
type: z.literal("tuple"),
|
|
501
|
+
value: z.array(TupleValueItem),
|
|
502
|
+
hidden: z.boolean().optional()
|
|
503
|
+
});
|
|
504
|
+
var LayerValueItem = z.union([
|
|
505
|
+
UnitValue,
|
|
506
|
+
KeywordValue,
|
|
507
|
+
UnparsedValue,
|
|
508
|
+
ImageValue,
|
|
509
|
+
TupleValue,
|
|
510
|
+
InvalidValue
|
|
511
|
+
]);
|
|
512
|
+
var LayersValue = z.object({
|
|
513
|
+
type: z.literal("layers"),
|
|
514
|
+
value: z.array(LayerValueItem)
|
|
515
|
+
});
|
|
516
|
+
var ValidStaticStyleValue = z.union([
|
|
517
|
+
ImageValue,
|
|
518
|
+
LayersValue,
|
|
519
|
+
UnitValue,
|
|
520
|
+
KeywordValue,
|
|
521
|
+
FontFamilyValue,
|
|
522
|
+
RgbValue,
|
|
523
|
+
UnparsedValue,
|
|
524
|
+
TupleValue
|
|
525
|
+
]);
|
|
526
|
+
var isValidStaticStyleValue = (styleValue) => {
|
|
527
|
+
const staticStyleValue = styleValue;
|
|
528
|
+
return staticStyleValue.type === "image" || staticStyleValue.type === "layers" || staticStyleValue.type === "unit" || staticStyleValue.type === "keyword" || staticStyleValue.type === "fontFamily" || staticStyleValue.type === "rgb" || staticStyleValue.type === "unparsed" || staticStyleValue.type === "tuple";
|
|
529
|
+
};
|
|
530
|
+
var VarValue = z.object({
|
|
531
|
+
type: z.literal("var"),
|
|
532
|
+
value: z.string(),
|
|
533
|
+
fallbacks: z.array(ValidStaticStyleValue)
|
|
534
|
+
});
|
|
535
|
+
var StyleValue = z.union([
|
|
536
|
+
ValidStaticStyleValue,
|
|
537
|
+
InvalidValue,
|
|
538
|
+
UnsetValue,
|
|
539
|
+
VarValue
|
|
540
|
+
]);
|
|
541
|
+
var Style = z.record(z.string(), StyleValue);
|
|
542
|
+
export {
|
|
543
|
+
CssEngine,
|
|
544
|
+
ImageValue,
|
|
545
|
+
InvalidValue,
|
|
546
|
+
KeywordValue,
|
|
547
|
+
LayersValue,
|
|
548
|
+
StyleValue,
|
|
549
|
+
TupleValue,
|
|
550
|
+
TupleValueItem,
|
|
551
|
+
UnitValue,
|
|
552
|
+
UnparsedValue,
|
|
553
|
+
compareMedia,
|
|
554
|
+
createCssEngine,
|
|
555
|
+
equalMedia,
|
|
556
|
+
findApplicableMedia,
|
|
557
|
+
isValidStaticStyleValue,
|
|
558
|
+
matchMedia,
|
|
559
|
+
toProperty,
|
|
560
|
+
toValue
|
|
561
|
+
};
|
package/package.json
CHANGED
|
@@ -1,14 +1,14 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@webstudio-is/css-engine",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.97.0",
|
|
4
4
|
"description": "CSS Renderer for Webstudio",
|
|
5
5
|
"author": "Webstudio <github@webstudio.is>",
|
|
6
6
|
"homepage": "https://webstudio.is",
|
|
7
7
|
"type": "module",
|
|
8
8
|
"dependencies": {
|
|
9
9
|
"hyphenate-style-name": "^1.0.4",
|
|
10
|
-
"@webstudio-is/error-utils": "^0.
|
|
11
|
-
"@webstudio-is/fonts": "^0.
|
|
10
|
+
"@webstudio-is/error-utils": "^0.97.0",
|
|
11
|
+
"@webstudio-is/fonts": "^0.97.0"
|
|
12
12
|
},
|
|
13
13
|
"devDependencies": {
|
|
14
14
|
"@jest/globals": "^29.6.4",
|
|
@@ -39,8 +39,8 @@
|
|
|
39
39
|
"scripts": {
|
|
40
40
|
"typecheck": "tsc",
|
|
41
41
|
"checks": "pnpm typecheck && pnpm test",
|
|
42
|
-
"dev": "
|
|
43
|
-
"build": "rm -rf lib && esbuild
|
|
42
|
+
"dev": "rm -rf lib && esbuild 'src/**/*.ts' 'src/**/*.tsx' --outdir=lib --watch",
|
|
43
|
+
"build": "rm -rf lib && esbuild src/index.ts --outdir=lib --bundle --format=esm --packages=external",
|
|
44
44
|
"dts": "tsc --project tsconfig.dts.json",
|
|
45
45
|
"test": "NODE_OPTIONS=--experimental-vm-modules jest",
|
|
46
46
|
"storybook:dev": "storybook dev -p 6006",
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
"use strict";
|
|
@@ -1,16 +0,0 @@
|
|
|
1
|
-
"use strict";
|
|
2
|
-
export const compareMedia = (optionA, optionB) => {
|
|
3
|
-
if (optionA.minWidth === void 0 && optionA.maxWidth === void 0) {
|
|
4
|
-
return -1;
|
|
5
|
-
}
|
|
6
|
-
if (optionB.minWidth === void 0 && optionB.maxWidth === void 0) {
|
|
7
|
-
return 1;
|
|
8
|
-
}
|
|
9
|
-
if (optionA.minWidth !== void 0 && optionB.minWidth !== void 0) {
|
|
10
|
-
return optionA.minWidth - optionB.minWidth;
|
|
11
|
-
}
|
|
12
|
-
if (optionA.maxWidth !== void 0 && optionB.maxWidth !== void 0) {
|
|
13
|
-
return optionB.maxWidth - optionA.maxWidth;
|
|
14
|
-
}
|
|
15
|
-
return "minWidth" in optionA ? 1 : -1;
|
|
16
|
-
};
|
|
@@ -1,63 +0,0 @@
|
|
|
1
|
-
"use strict";
|
|
2
|
-
import { describe, test, expect } from "@jest/globals";
|
|
3
|
-
import { compareMedia } from "./compare-media";
|
|
4
|
-
describe("Compare media", () => {
|
|
5
|
-
test("min-width", () => {
|
|
6
|
-
const initial = [
|
|
7
|
-
{},
|
|
8
|
-
{ minWidth: 1280 },
|
|
9
|
-
{ minWidth: 0 },
|
|
10
|
-
{ minWidth: 1024 },
|
|
11
|
-
{ minWidth: 768 }
|
|
12
|
-
];
|
|
13
|
-
const expected = [
|
|
14
|
-
{},
|
|
15
|
-
{ minWidth: 0 },
|
|
16
|
-
{ minWidth: 768 },
|
|
17
|
-
{ minWidth: 1024 },
|
|
18
|
-
{ minWidth: 1280 }
|
|
19
|
-
];
|
|
20
|
-
const sorted = initial.sort(compareMedia);
|
|
21
|
-
expect(sorted).toStrictEqual(expected);
|
|
22
|
-
});
|
|
23
|
-
test("max-width", () => {
|
|
24
|
-
const initial = [
|
|
25
|
-
{},
|
|
26
|
-
{ maxWidth: 1280 },
|
|
27
|
-
{ maxWidth: 0 },
|
|
28
|
-
{ maxWidth: 1024 },
|
|
29
|
-
{ maxWidth: 768 }
|
|
30
|
-
];
|
|
31
|
-
const expected = [
|
|
32
|
-
{},
|
|
33
|
-
{ maxWidth: 1280 },
|
|
34
|
-
{ maxWidth: 1024 },
|
|
35
|
-
{ maxWidth: 768 },
|
|
36
|
-
{ maxWidth: 0 }
|
|
37
|
-
];
|
|
38
|
-
const sorted = initial.sort(compareMedia);
|
|
39
|
-
expect(sorted).toStrictEqual(expected);
|
|
40
|
-
});
|
|
41
|
-
test("mixed max and min", () => {
|
|
42
|
-
const initial = [
|
|
43
|
-
{},
|
|
44
|
-
{ maxWidth: 991 },
|
|
45
|
-
{ maxWidth: 479 },
|
|
46
|
-
{ maxWidth: 767 },
|
|
47
|
-
{ minWidth: 1440 },
|
|
48
|
-
{ minWidth: 1280 },
|
|
49
|
-
{ minWidth: 1920 }
|
|
50
|
-
];
|
|
51
|
-
const expected = [
|
|
52
|
-
{},
|
|
53
|
-
{ maxWidth: 991 },
|
|
54
|
-
{ maxWidth: 767 },
|
|
55
|
-
{ maxWidth: 479 },
|
|
56
|
-
{ minWidth: 1280 },
|
|
57
|
-
{ minWidth: 1440 },
|
|
58
|
-
{ minWidth: 1920 }
|
|
59
|
-
];
|
|
60
|
-
const sorted = initial.sort(compareMedia);
|
|
61
|
-
expect(sorted).toStrictEqual(expected);
|
|
62
|
-
});
|
|
63
|
-
});
|