@webstudio-is/css-engine 0.95.0 → 0.96.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.
@@ -1,22 +0,0 @@
1
- "use strict";
2
- import { describe, expect, test } from "@jest/globals";
3
- import { matchMedia } from "./match-media";
4
- describe("matchMedia", () => {
5
- test("minWidth", () => {
6
- expect(matchMedia({ minWidth: 100 }, 10)).toBe(false);
7
- expect(matchMedia({ minWidth: 100 }, 100)).toBe(true);
8
- expect(matchMedia({ minWidth: 100 }, 101)).toBe(true);
9
- });
10
- test("maxWidth", () => {
11
- expect(matchMedia({ maxWidth: 100 }, 101)).toBe(false);
12
- expect(matchMedia({ maxWidth: 100 }, 100)).toBe(true);
13
- expect(matchMedia({ maxWidth: 100 }, 10)).toBe(true);
14
- });
15
- test("minWidth and maxWidth", () => {
16
- expect(matchMedia({ maxWidth: 100, minWidth: 10 }, 9)).toBe(false);
17
- expect(matchMedia({ maxWidth: 100, minWidth: 10 }, 101)).toBe(false);
18
- expect(matchMedia({ maxWidth: 100, minWidth: 10 }, 100)).toBe(true);
19
- expect(matchMedia({ maxWidth: 100, minWidth: 10 }, 10)).toBe(true);
20
- expect(matchMedia({ maxWidth: 100, minWidth: 10 }, 11)).toBe(true);
21
- });
22
- });
package/lib/core/rules.js DELETED
@@ -1,162 +0,0 @@
1
- "use strict";
2
- import { toValue } from "./to-value";
3
- import { toProperty } from "./to-property";
4
- class StylePropertyMap {
5
- #styleMap = /* @__PURE__ */ new Map();
6
- #isDirty = false;
7
- #string = "";
8
- #indent = 0;
9
- #transformValue;
10
- onChange;
11
- constructor(transformValue) {
12
- this.#transformValue = transformValue;
13
- }
14
- setTransformer(transformValue) {
15
- this.#transformValue = transformValue;
16
- }
17
- set(property, value) {
18
- this.#styleMap.set(property, value);
19
- this.#isDirty = true;
20
- this.onChange?.();
21
- }
22
- has(property) {
23
- return this.#styleMap.has(property);
24
- }
25
- get size() {
26
- return this.#styleMap.size;
27
- }
28
- keys() {
29
- return this.#styleMap.keys();
30
- }
31
- delete(property) {
32
- this.#styleMap.delete(property);
33
- this.#isDirty = true;
34
- this.onChange?.();
35
- }
36
- clear() {
37
- this.#styleMap.clear();
38
- this.#isDirty = true;
39
- this.onChange?.();
40
- }
41
- toString({ indent = 0 } = {}) {
42
- if (this.#isDirty === false && indent === this.#indent) {
43
- return this.#string;
44
- }
45
- this.#indent = indent;
46
- const block = [];
47
- const spaces = " ".repeat(indent);
48
- for (const [property, value] of this.#styleMap) {
49
- if (value === void 0) {
50
- continue;
51
- }
52
- block.push(
53
- `${spaces}${toProperty(property)}: ${toValue(
54
- value,
55
- this.#transformValue
56
- )}`
57
- );
58
- }
59
- this.#string = block.join(";\n");
60
- this.#isDirty = false;
61
- return this.#string;
62
- }
63
- }
64
- export class StyleRule {
65
- styleMap;
66
- selectorText;
67
- onChange;
68
- constructor(selectorText, style, transformValue) {
69
- this.styleMap = new StylePropertyMap(transformValue);
70
- this.selectorText = selectorText;
71
- let property;
72
- for (property in style) {
73
- this.styleMap.set(property, style[property]);
74
- }
75
- this.styleMap.onChange = this.#onChange;
76
- }
77
- #onChange = () => {
78
- this.onChange?.();
79
- };
80
- get cssText() {
81
- return this.toString();
82
- }
83
- toString(options = { indent: 0 }) {
84
- const spaces = " ".repeat(options.indent);
85
- return `${spaces}${this.selectorText} {
86
- ${this.styleMap.toString({
87
- indent: options.indent + 2
88
- })}
89
- ${spaces}}`;
90
- }
91
- }
92
- export class MediaRule {
93
- options;
94
- rules = [];
95
- #mediaType;
96
- constructor(options = {}) {
97
- this.options = options;
98
- this.#mediaType = options.mediaType ?? "all";
99
- }
100
- insertRule(rule) {
101
- this.rules.push(rule);
102
- return rule;
103
- }
104
- get cssText() {
105
- return this.toString();
106
- }
107
- toString() {
108
- if (this.rules.length === 0) {
109
- return "";
110
- }
111
- const rules = [];
112
- for (const rule of this.rules) {
113
- rules.push(rule.toString({ indent: 2 }));
114
- }
115
- let conditionText = "";
116
- const { minWidth, maxWidth } = this.options;
117
- if (minWidth !== void 0) {
118
- conditionText = ` and (min-width: ${minWidth}px)`;
119
- }
120
- if (maxWidth !== void 0) {
121
- conditionText += ` and (max-width: ${maxWidth}px)`;
122
- }
123
- return `@media ${this.#mediaType}${conditionText} {
124
- ${rules.join(
125
- "\n"
126
- )}
127
- }`;
128
- }
129
- }
130
- export class PlaintextRule {
131
- cssText;
132
- styleMap = new StylePropertyMap();
133
- constructor(cssText) {
134
- this.cssText = cssText;
135
- }
136
- toString() {
137
- return this.cssText;
138
- }
139
- }
140
- export class FontFaceRule {
141
- options;
142
- constructor(options) {
143
- this.options = options;
144
- }
145
- get cssText() {
146
- return this.toString();
147
- }
148
- toString() {
149
- const decls = [];
150
- const { fontFamily, fontStyle, fontWeight, fontDisplay, src } = this.options;
151
- decls.push(
152
- `font-family: ${/\s/.test(fontFamily) ? `"${fontFamily}"` : fontFamily}`
153
- );
154
- decls.push(`font-style: ${fontStyle}`);
155
- decls.push(`font-weight: ${fontWeight}`);
156
- decls.push(`font-display: ${fontDisplay}`);
157
- decls.push(`src: ${src}`);
158
- return `@font-face {
159
- ${decls.join("; ")};
160
- }`;
161
- }
162
- }
@@ -1,39 +0,0 @@
1
- "use strict";
2
- export class StyleElement {
3
- #element;
4
- #name;
5
- constructor(name = "") {
6
- this.#name = name;
7
- }
8
- get isMounted() {
9
- return this.#element?.parentElement != null;
10
- }
11
- mount() {
12
- if (this.isMounted === false) {
13
- this.#element = document.createElement("style");
14
- this.#element.setAttribute("data-webstudio", this.#name);
15
- document.head.appendChild(this.#element);
16
- }
17
- }
18
- unmount() {
19
- if (this.isMounted) {
20
- this.#element?.parentElement?.removeChild(this.#element);
21
- this.#element = void 0;
22
- }
23
- }
24
- render(cssText) {
25
- if (this.#element) {
26
- this.#element.textContent = cssText;
27
- }
28
- }
29
- setAttribute(name, value) {
30
- if (this.#element) {
31
- this.#element.setAttribute(name, value);
32
- }
33
- }
34
- getAttribute(name) {
35
- if (this.#element) {
36
- return this.#element.getAttribute(name);
37
- }
38
- }
39
- }
@@ -1,14 +0,0 @@
1
- "use strict";
2
- export class StyleSheet {
3
- #cssText = "";
4
- #element;
5
- constructor(element) {
6
- this.#element = element;
7
- }
8
- replaceSync(cssText) {
9
- if (cssText !== this.#cssText) {
10
- this.#cssText = cssText;
11
- this.#element.render(cssText);
12
- }
13
- }
14
- }
@@ -1,8 +0,0 @@
1
- "use strict";
2
- import hyphenate from "hyphenate-style-name";
3
- export const toProperty = (property) => {
4
- if (property === "backgroundClip") {
5
- return "-webkit-background-clip";
6
- }
7
- return hyphenate(property);
8
- };
@@ -1,11 +0,0 @@
1
- "use strict";
2
- import { describe, test, expect } from "@jest/globals";
3
- import { toProperty } from "./to-property";
4
- describe("toProperty", () => {
5
- test("boxSizing", () => {
6
- expect(toProperty("boxSizing")).toBe("box-sizing");
7
- });
8
- test("backgroundClip", () => {
9
- expect(toProperty("backgroundClip")).toBe("-webkit-background-clip");
10
- });
11
- });
@@ -1,76 +0,0 @@
1
- "use strict";
2
- import { captureError } from "@webstudio-is/error-utils";
3
- import { DEFAULT_FONT_FALLBACK, SYSTEM_FONTS } from "@webstudio-is/fonts";
4
- const 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
- export const 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
- };
@@ -1,123 +0,0 @@
1
- "use strict";
2
- import { describe, test, expect } from "@jest/globals";
3
- import { toValue } from "./to-value";
4
- describe("Convert WS CSS Values to native CSS strings", () => {
5
- test("keyword", () => {
6
- const value = toValue({ type: "keyword", value: "red" });
7
- expect(value).toBe("red");
8
- });
9
- test("unit", () => {
10
- const value = toValue({ type: "unit", value: 10, unit: "px" });
11
- expect(value).toBe("10px");
12
- });
13
- test("invalid", () => {
14
- const value = toValue({ type: "invalid", value: "bad" });
15
- expect(value).toBe("bad");
16
- });
17
- test("unset", () => {
18
- const value = toValue({ type: "unset", value: "" });
19
- expect(value).toBe("");
20
- });
21
- test("var", () => {
22
- const value = toValue({ type: "var", value: "namespace", fallbacks: [] });
23
- expect(value).toBe("var(--namespace)");
24
- });
25
- test("var with fallbacks", () => {
26
- const value = toValue({
27
- type: "var",
28
- value: "namespace",
29
- fallbacks: [
30
- {
31
- type: "keyword",
32
- value: "normal"
33
- },
34
- {
35
- type: "unit",
36
- value: 10,
37
- unit: "px"
38
- }
39
- ]
40
- });
41
- expect(value).toBe("var(--namespace, normal, 10px)");
42
- });
43
- test("fontFamily", () => {
44
- const value = toValue({
45
- type: "fontFamily",
46
- value: ["Courier New"]
47
- });
48
- expect(value).toBe("Courier New, monospace");
49
- });
50
- test("Transform font family value to override default fallback", () => {
51
- const value = toValue(
52
- {
53
- type: "fontFamily",
54
- value: ["Courier New"]
55
- },
56
- (styleValue) => {
57
- if (styleValue.type === "fontFamily") {
58
- return {
59
- type: "fontFamily",
60
- value: [styleValue.value[0]]
61
- };
62
- }
63
- }
64
- );
65
- expect(value).toBe("Courier New");
66
- });
67
- test("array", () => {
68
- const assets = /* @__PURE__ */ new Map([
69
- ["1234567890", { path: "foo.png" }]
70
- ]);
71
- const value = toValue(
72
- {
73
- type: "layers",
74
- value: [
75
- {
76
- type: "keyword",
77
- value: "auto"
78
- },
79
- { type: "unit", value: 10, unit: "px" },
80
- { type: "unparsed", value: "calc(10px)" },
81
- {
82
- type: "image",
83
- value: {
84
- type: "asset",
85
- value: "1234567890"
86
- }
87
- }
88
- ]
89
- },
90
- (styleValue) => {
91
- if (styleValue.type === "image" && styleValue.value.type === "asset") {
92
- const asset = assets.get(styleValue.value.value);
93
- if (asset === void 0) {
94
- return {
95
- type: "keyword",
96
- value: "none"
97
- };
98
- }
99
- return {
100
- type: "image",
101
- value: {
102
- type: "url",
103
- url: asset.path
104
- }
105
- };
106
- }
107
- }
108
- );
109
- expect(value).toBe("auto, 10px, calc(10px), url(foo.png)");
110
- });
111
- test("tuple", () => {
112
- const value = toValue({
113
- type: "tuple",
114
- value: [
115
- { type: "unit", value: 10, unit: "px" },
116
- { type: "unit", value: 20, unit: "px" },
117
- { type: "unit", value: 30, unit: "px" },
118
- { type: "unit", value: 40, unit: "px" }
119
- ]
120
- });
121
- expect(value).toBe("10px 20px 30px 40px");
122
- });
123
- });
package/lib/schema.js DELETED
@@ -1,98 +0,0 @@
1
- "use strict";
2
- import { z } from "zod";
3
- const Unit = z.string();
4
- export const UnitValue = z.object({
5
- type: z.literal("unit"),
6
- unit: Unit,
7
- value: z.number()
8
- });
9
- export const KeywordValue = z.object({
10
- type: z.literal("keyword"),
11
- // @todo use exact type
12
- value: z.string()
13
- });
14
- export const UnparsedValue = z.object({
15
- type: z.literal("unparsed"),
16
- value: z.string(),
17
- // For the builder we want to be able to hide background-image
18
- hidden: z.boolean().optional()
19
- });
20
- const FontFamilyValue = z.object({
21
- type: z.literal("fontFamily"),
22
- value: z.array(z.string())
23
- });
24
- const RgbValue = z.object({
25
- type: z.literal("rgb"),
26
- r: z.number(),
27
- g: z.number(),
28
- b: z.number(),
29
- alpha: z.number()
30
- });
31
- export const ImageValue = z.object({
32
- type: z.literal("image"),
33
- value: z.union([
34
- z.object({ type: z.literal("asset"), value: z.string() }),
35
- // url is not stored in db and only used by css-engine transformValue
36
- // to prepare image value for rendering
37
- z.object({ type: z.literal("url"), url: z.string() })
38
- ]),
39
- // For the builder we want to be able to hide images
40
- hidden: z.boolean().optional()
41
- });
42
- export const InvalidValue = z.object({
43
- type: z.literal("invalid"),
44
- value: z.string()
45
- });
46
- const UnsetValue = z.object({
47
- type: z.literal("unset"),
48
- value: z.literal("")
49
- });
50
- export const TupleValueItem = z.union([
51
- UnitValue,
52
- KeywordValue,
53
- UnparsedValue,
54
- RgbValue
55
- ]);
56
- export const TupleValue = z.object({
57
- type: z.literal("tuple"),
58
- value: z.array(TupleValueItem),
59
- hidden: z.boolean().optional()
60
- });
61
- const LayerValueItem = z.union([
62
- UnitValue,
63
- KeywordValue,
64
- UnparsedValue,
65
- ImageValue,
66
- TupleValue,
67
- InvalidValue
68
- ]);
69
- export const LayersValue = z.object({
70
- type: z.literal("layers"),
71
- value: z.array(LayerValueItem)
72
- });
73
- const ValidStaticStyleValue = z.union([
74
- ImageValue,
75
- LayersValue,
76
- UnitValue,
77
- KeywordValue,
78
- FontFamilyValue,
79
- RgbValue,
80
- UnparsedValue,
81
- TupleValue
82
- ]);
83
- export const isValidStaticStyleValue = (styleValue) => {
84
- const staticStyleValue = styleValue;
85
- 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";
86
- };
87
- const VarValue = z.object({
88
- type: z.literal("var"),
89
- value: z.string(),
90
- fallbacks: z.array(ValidStaticStyleValue)
91
- });
92
- export const StyleValue = z.union([
93
- ValidStaticStyleValue,
94
- InvalidValue,
95
- UnsetValue,
96
- VarValue
97
- ]);
98
- const Style = z.record(z.string(), StyleValue);