@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.
@@ -1,105 +0,0 @@
1
- "use strict";
2
- import {
3
- FontFaceRule,
4
- MediaRule,
5
- PlaintextRule,
6
- StyleRule
7
- } from "./rules";
8
- import { compareMedia } from "./compare-media";
9
- import { StyleElement } from "./style-element";
10
- import { StyleSheet } from "./style-sheet";
11
- const defaultMediaRuleId = "__default-media-rule__";
12
- export class CssEngine {
13
- #element;
14
- #mediaRules = /* @__PURE__ */ new Map();
15
- #plainRules = /* @__PURE__ */ new Map();
16
- #fontFaceRules = [];
17
- #sheet;
18
- #isDirty = false;
19
- #cssText = "";
20
- constructor({ name }) {
21
- this.#element = new StyleElement(name);
22
- this.#sheet = new StyleSheet(this.#element);
23
- }
24
- addMediaRule(id, options) {
25
- let mediaRule = this.#mediaRules.get(id);
26
- if (mediaRule === void 0) {
27
- mediaRule = new MediaRule(options);
28
- this.#mediaRules.set(id, mediaRule);
29
- this.#isDirty = true;
30
- return mediaRule;
31
- }
32
- if (options) {
33
- mediaRule.options = options;
34
- this.#isDirty = true;
35
- }
36
- return mediaRule;
37
- }
38
- addStyleRule(selectorText, rule, transformValue) {
39
- const mediaRule = this.addMediaRule(rule.breakpoint || defaultMediaRuleId);
40
- this.#isDirty = true;
41
- const styleRule = new StyleRule(selectorText, rule.style, transformValue);
42
- styleRule.onChange = this.#onChangeRule;
43
- if (mediaRule === void 0) {
44
- throw new Error("No media rule found");
45
- }
46
- mediaRule.insertRule(styleRule);
47
- return styleRule;
48
- }
49
- addPlaintextRule(cssText) {
50
- const rule = this.#plainRules.get(cssText);
51
- if (rule !== void 0) {
52
- return rule;
53
- }
54
- this.#isDirty = true;
55
- return this.#plainRules.set(cssText, new PlaintextRule(cssText));
56
- }
57
- addFontFaceRule(options) {
58
- this.#isDirty = true;
59
- return this.#fontFaceRules.push(new FontFaceRule(options));
60
- }
61
- clear() {
62
- this.#mediaRules.clear();
63
- this.#plainRules.clear();
64
- this.#fontFaceRules = [];
65
- this.#isDirty = true;
66
- }
67
- render() {
68
- this.#element.mount();
69
- this.#sheet.replaceSync(this.cssText);
70
- }
71
- unmount() {
72
- this.#element.unmount();
73
- }
74
- setAttribute(name, value) {
75
- this.#element.setAttribute(name, value);
76
- }
77
- getAttribute(name) {
78
- return this.#element.getAttribute(name);
79
- }
80
- get cssText() {
81
- if (this.#isDirty === false) {
82
- return this.#cssText;
83
- }
84
- this.#isDirty = false;
85
- const css = [];
86
- css.push(...this.#fontFaceRules.map((rule) => rule.cssText));
87
- for (const plaintextRule of this.#plainRules.values()) {
88
- css.push(plaintextRule.cssText);
89
- }
90
- const sortedMediaRules = Array.from(this.#mediaRules.values()).sort(
91
- (ruleA, ruleB) => compareMedia(ruleA.options, ruleB.options)
92
- );
93
- for (const mediaRule of sortedMediaRules) {
94
- const { cssText } = mediaRule;
95
- if (cssText !== "") {
96
- css.push(cssText);
97
- }
98
- }
99
- this.#cssText = css.join("\n");
100
- return this.#cssText;
101
- }
102
- #onChangeRule = () => {
103
- this.#isDirty = true;
104
- };
105
- }
@@ -1,48 +0,0 @@
1
- "use strict";
2
- import { Fragment, jsx, jsxs } from "react/jsx-runtime";
3
- import { CssEngine } from "./css-engine";
4
- export default {
5
- component: "CssEngine"
6
- };
7
- const style0 = {
8
- color: { type: "keyword", value: "red" }
9
- };
10
- const mediaRuleOptions0 = { minWidth: 0 };
11
- const mediaId = "0";
12
- export const Basic = () => {
13
- const engine = new CssEngine({ name: "test" });
14
- engine.addMediaRule(mediaId, mediaRuleOptions0);
15
- const rule = engine.addStyleRule(".test", {
16
- style: style0,
17
- breakpoint: "0"
18
- });
19
- engine.render();
20
- return /* @__PURE__ */ jsxs(Fragment, { children: [
21
- /* @__PURE__ */ jsx("div", { className: "test", children: "Should be red" }),
22
- /* @__PURE__ */ jsx(
23
- "button",
24
- {
25
- onClick: () => {
26
- rule.styleMap.set("color", { type: "keyword", value: "green" });
27
- engine.render();
28
- },
29
- children: "Make it green"
30
- }
31
- ),
32
- /* @__PURE__ */ jsx(
33
- "button",
34
- {
35
- onClick: () => {
36
- engine.addStyleRule(".test", {
37
- style: {
38
- backgroundColor: { type: "keyword", value: "yellow" }
39
- },
40
- breakpoint: "0"
41
- });
42
- engine.render();
43
- },
44
- children: "Add rule with yellow background"
45
- }
46
- )
47
- ] });
48
- };
@@ -1,451 +0,0 @@
1
- "use strict";
2
- import { describe, beforeEach, test, expect } from "@jest/globals";
3
- import { CssEngine } from "./css-engine";
4
- const style0 = {
5
- display: { type: "keyword", value: "block" }
6
- };
7
- const mediaRuleOptions0 = { minWidth: 0 };
8
- const mediaId0 = "0";
9
- const style1 = {
10
- display: { type: "keyword", value: "flex" }
11
- };
12
- const style2 = {
13
- display: { type: "keyword", value: "block" },
14
- color: { type: "keyword", value: "black" }
15
- };
16
- const mediaRuleOptions1 = { minWidth: 300 };
17
- const mediaId1 = "1";
18
- describe("CssEngine", () => {
19
- let engine;
20
- const reset = () => {
21
- engine = new CssEngine({ name: "test" });
22
- };
23
- beforeEach(reset);
24
- test("minWidth media rule", () => {
25
- engine.addMediaRule("0", { minWidth: 0 });
26
- engine.addStyleRule(".c1", {
27
- style: { color: { type: "keyword", value: "red" } },
28
- breakpoint: "0"
29
- });
30
- expect(engine.cssText).toMatchInlineSnapshot(`
31
- "@media all and (min-width: 0px) {
32
- .c1 {
33
- color: red
34
- }
35
- }"
36
- `);
37
- });
38
- test("maxWidth media rule", () => {
39
- engine.addMediaRule("0", { maxWidth: 1e3 });
40
- engine.addStyleRule(".c1", {
41
- style: { color: { type: "keyword", value: "red" } },
42
- breakpoint: "0"
43
- });
44
- expect(engine.cssText).toMatchInlineSnapshot(`
45
- "@media all and (max-width: 1000px) {
46
- .c1 {
47
- color: red
48
- }
49
- }"
50
- `);
51
- });
52
- test("maxWidth and maxWith media rule", () => {
53
- engine.addMediaRule("0", { maxWidth: 1e3, minWidth: 360 });
54
- engine.addStyleRule(".c1", {
55
- style: { color: { type: "keyword", value: "red" } },
56
- breakpoint: "0"
57
- });
58
- expect(engine.cssText).toMatchInlineSnapshot(`
59
- "@media all and (min-width: 360px) and (max-width: 1000px) {
60
- .c1 {
61
- color: red
62
- }
63
- }"
64
- `);
65
- });
66
- test("use default media rule when there is no matching one registered", () => {
67
- engine.addStyleRule(".c", {
68
- style: style0,
69
- breakpoint: "x"
70
- });
71
- expect(engine.cssText).toMatchInlineSnapshot(`
72
- "@media all {
73
- .c {
74
- display: block
75
- }
76
- }"
77
- `);
78
- engine.addStyleRule(".c1", {
79
- style: { color: { type: "keyword", value: "red" } },
80
- breakpoint: "x"
81
- });
82
- expect(engine.cssText).toMatchInlineSnapshot(`
83
- "@media all {
84
- .c {
85
- display: block
86
- }
87
- .c1 {
88
- color: red
89
- }
90
- }"
91
- `);
92
- engine.addMediaRule(mediaId0, mediaRuleOptions0);
93
- engine.addStyleRule(".c1", {
94
- style: { color: { type: "keyword", value: "blue" } },
95
- breakpoint: mediaId0
96
- });
97
- expect(engine.cssText).toMatchInlineSnapshot(`
98
- "@media all {
99
- .c {
100
- display: block
101
- }
102
- .c1 {
103
- color: red
104
- }
105
- }
106
- @media all and (min-width: 0px) {
107
- .c1 {
108
- color: blue
109
- }
110
- }"
111
- `);
112
- });
113
- test("sort media queries based on lower min-width", () => {
114
- engine.addMediaRule(mediaId1, mediaRuleOptions1);
115
- engine.addStyleRule(".c2", {
116
- style: style1,
117
- breakpoint: mediaId1
118
- });
119
- engine.addMediaRule(mediaId0, mediaRuleOptions0);
120
- engine.addStyleRule(".c1", {
121
- style: style0,
122
- breakpoint: mediaId0
123
- });
124
- engine.addStyleRule(".c3", {
125
- style: style0,
126
- breakpoint: "x"
127
- });
128
- expect(engine.cssText).toMatchInlineSnapshot(`
129
- "@media all {
130
- .c3 {
131
- display: block
132
- }
133
- }
134
- @media all and (min-width: 0px) {
135
- .c1 {
136
- display: block
137
- }
138
- }
139
- @media all and (min-width: 300px) {
140
- .c2 {
141
- display: flex
142
- }
143
- }"
144
- `);
145
- });
146
- test("keep the sort order when minWidth is not defined", () => {
147
- engine.addStyleRule(".c0", {
148
- style: style0,
149
- breakpoint: "x"
150
- });
151
- engine.addStyleRule(".c1", {
152
- style: style1,
153
- breakpoint: "x"
154
- });
155
- expect(engine.cssText).toMatchInlineSnapshot(`
156
- "@media all {
157
- .c0 {
158
- display: block
159
- }
160
- .c1 {
161
- display: flex
162
- }
163
- }"
164
- `);
165
- reset();
166
- engine.addStyleRule(".c1", {
167
- style: style1,
168
- breakpoint: "x"
169
- });
170
- engine.addStyleRule(".c0", {
171
- style: style0,
172
- breakpoint: "x"
173
- });
174
- expect(engine.cssText).toMatchInlineSnapshot(`
175
- "@media all {
176
- .c1 {
177
- display: flex
178
- }
179
- .c0 {
180
- display: block
181
- }
182
- }"
183
- `);
184
- });
185
- test("rule with multiple properties", () => {
186
- engine.addMediaRule(mediaId0, mediaRuleOptions0);
187
- engine.addStyleRule(".c", {
188
- style: {
189
- ...style0,
190
- color: { type: "keyword", value: "red" }
191
- },
192
- breakpoint: "0"
193
- });
194
- expect(engine.cssText).toMatchInlineSnapshot(`
195
- "@media all and (min-width: 0px) {
196
- .c {
197
- display: block;
198
- color: red
199
- }
200
- }"
201
- `);
202
- });
203
- test("hyphenate property", () => {
204
- engine.addMediaRule(mediaId0, mediaRuleOptions0);
205
- engine.addStyleRule(".c", {
206
- style: {
207
- backgroundColor: { type: "keyword", value: "red" }
208
- },
209
- breakpoint: "0"
210
- });
211
- expect(engine.cssText).toMatchInlineSnapshot(`
212
- "@media all and (min-width: 0px) {
213
- .c {
214
- background-color: red
215
- }
216
- }"
217
- `);
218
- });
219
- test("add rule", () => {
220
- engine.addMediaRule(mediaId0, mediaRuleOptions0);
221
- const rule1 = engine.addStyleRule(".c", {
222
- style: {
223
- ...style0,
224
- color: { type: "keyword", value: "red" }
225
- },
226
- breakpoint: "0"
227
- });
228
- expect(engine.cssText).toMatchInlineSnapshot(`
229
- "@media all and (min-width: 0px) {
230
- .c {
231
- display: block;
232
- color: red
233
- }
234
- }"
235
- `);
236
- expect(rule1.cssText).toMatchInlineSnapshot(`
237
- ".c {
238
- display: block;
239
- color: red
240
- }"
241
- `);
242
- engine.addStyleRule(".c2", {
243
- style: {
244
- ...style0,
245
- color: { type: "keyword", value: "green" }
246
- },
247
- breakpoint: "0"
248
- });
249
- expect(engine.cssText).toMatchInlineSnapshot(`
250
- "@media all and (min-width: 0px) {
251
- .c {
252
- display: block;
253
- color: red
254
- }
255
- .c2 {
256
- display: block;
257
- color: green
258
- }
259
- }"
260
- `);
261
- });
262
- test("update rule", () => {
263
- engine.addMediaRule(mediaId0, mediaRuleOptions0);
264
- const rule = engine.addStyleRule(".c", {
265
- style: {
266
- ...style0,
267
- color: { type: "keyword", value: "red" }
268
- },
269
- breakpoint: "0"
270
- });
271
- expect(engine.cssText).toMatchInlineSnapshot(`
272
- "@media all and (min-width: 0px) {
273
- .c {
274
- display: block;
275
- color: red
276
- }
277
- }"
278
- `);
279
- expect(rule.cssText).toMatchInlineSnapshot(`
280
- ".c {
281
- display: block;
282
- color: red
283
- }"
284
- `);
285
- rule.styleMap.set("color", { type: "keyword", value: "green" });
286
- expect(rule.cssText).toMatchInlineSnapshot(`
287
- ".c {
288
- display: block;
289
- color: green
290
- }"
291
- `);
292
- expect(engine.cssText).toMatchInlineSnapshot(`
293
- "@media all and (min-width: 0px) {
294
- .c {
295
- display: block;
296
- color: green
297
- }
298
- }"
299
- `);
300
- });
301
- test("update media rule options", () => {
302
- engine.addMediaRule(mediaId0, mediaRuleOptions0);
303
- engine.addStyleRule(".c", {
304
- style: {
305
- color: { type: "keyword", value: "red" }
306
- },
307
- breakpoint: "0"
308
- });
309
- expect(engine.cssText).toMatchInlineSnapshot(`
310
- "@media all and (min-width: 0px) {
311
- .c {
312
- color: red
313
- }
314
- }"
315
- `);
316
- engine.addMediaRule(mediaId0, { minWidth: 10 });
317
- expect(engine.cssText).toMatchInlineSnapshot(`
318
- "@media all and (min-width: 10px) {
319
- .c {
320
- color: red
321
- }
322
- }"
323
- `);
324
- });
325
- test("don't override media queries", () => {
326
- engine.addMediaRule(mediaId0, mediaRuleOptions0);
327
- engine.addStyleRule(".c", {
328
- style: style0,
329
- breakpoint: "0"
330
- });
331
- expect(engine.cssText).toMatchInlineSnapshot(`
332
- "@media all and (min-width: 0px) {
333
- .c {
334
- display: block
335
- }
336
- }"
337
- `);
338
- engine.addMediaRule(mediaId0, mediaRuleOptions0);
339
- expect(engine.cssText).toMatchInlineSnapshot(`
340
- "@media all and (min-width: 0px) {
341
- .c {
342
- display: block
343
- }
344
- }"
345
- `);
346
- });
347
- test("plaintext rule", () => {
348
- engine.addPlaintextRule(".c { color: red }");
349
- expect(engine.cssText).toMatchInlineSnapshot(`".c { color: red }"`);
350
- });
351
- test("plaintext - no duplicates", () => {
352
- engine.addPlaintextRule(".c { color: red }");
353
- engine.addPlaintextRule(".c { color: red }");
354
- engine.addPlaintextRule(".c { color: green }");
355
- expect(engine.cssText).toMatchInlineSnapshot(`
356
- ".c { color: red }
357
- .c { color: green }"
358
- `);
359
- });
360
- test("font family rule", () => {
361
- engine.addFontFaceRule({
362
- fontFamily: "Roboto",
363
- fontStyle: "normal",
364
- fontWeight: 400,
365
- fontDisplay: "swap",
366
- src: "url(/src)"
367
- });
368
- expect(engine.cssText).toMatchInlineSnapshot(`
369
- "@font-face {
370
- font-family: Roboto; font-style: normal; font-weight: 400; font-display: swap; src: url(/src);
371
- }"
372
- `);
373
- });
374
- test("clear", () => {
375
- engine.addStyleRule(".c", {
376
- style: style0,
377
- breakpoint: "0"
378
- });
379
- engine.clear();
380
- expect(engine.cssText).toMatchInlineSnapshot(`""`);
381
- });
382
- test("get all rule style keys", () => {
383
- const rule = engine.addStyleRule(".c", {
384
- style: style2,
385
- breakpoint: "0"
386
- });
387
- expect(Array.from(rule.styleMap.keys())).toMatchInlineSnapshot(`
388
- [
389
- "display",
390
- "color",
391
- ]
392
- `);
393
- });
394
- test("delete style from rule", () => {
395
- const rule = engine.addStyleRule(".c", {
396
- style: style2,
397
- breakpoint: "0"
398
- });
399
- rule.styleMap.delete("display");
400
- expect(engine.cssText).toMatchInlineSnapshot(`
401
- "@media all {
402
- .c {
403
- color: black
404
- }
405
- }"
406
- `);
407
- });
408
- test("render images with injected asset url", () => {
409
- const assets = /* @__PURE__ */ new Map([
410
- ["1234", { path: "foo.png" }]
411
- ]);
412
- const rule = engine.addStyleRule(
413
- ".c",
414
- {
415
- style: {
416
- backgroundImage: {
417
- type: "image",
418
- value: {
419
- type: "asset",
420
- value: "1234"
421
- }
422
- }
423
- },
424
- breakpoint: "0"
425
- },
426
- (styleValue) => {
427
- if (styleValue.type === "image" && styleValue.value.type === "asset") {
428
- const asset = assets.get(styleValue.value.value);
429
- if (asset === void 0) {
430
- return { type: "keyword", value: "none" };
431
- }
432
- return {
433
- type: "image",
434
- value: {
435
- type: "url",
436
- url: asset.path
437
- }
438
- };
439
- }
440
- }
441
- );
442
- rule.styleMap.delete("display");
443
- expect(engine.cssText).toMatchInlineSnapshot(`
444
- "@media all {
445
- .c {
446
- background-image: url(foo.png)
447
- }
448
- }"
449
- `);
450
- });
451
- });
@@ -1,4 +0,0 @@
1
- "use strict";
2
- export const equalMedia = (left, right) => {
3
- return left.minWidth === right.minWidth && left.maxWidth === right.maxWidth;
4
- };
@@ -1,29 +0,0 @@
1
- "use strict";
2
- import { describe, expect, test } from "@jest/globals";
3
- import { equalMedia } from "./equal-media";
4
- describe("equalMedia", () => {
5
- test("minWidth", () => {
6
- expect(equalMedia({ minWidth: 100 }, { minWidth: 10 })).toBe(false);
7
- expect(equalMedia({ minWidth: 100 }, { minWidth: 100 })).toBe(true);
8
- expect(equalMedia({ minWidth: 100 }, { minWidth: 101 })).toBe(false);
9
- });
10
- test("maxWidth", () => {
11
- expect(equalMedia({ maxWidth: 100 }, { maxWidth: 101 })).toBe(false);
12
- expect(equalMedia({ maxWidth: 100 }, { maxWidth: 100 })).toBe(true);
13
- expect(equalMedia({ maxWidth: 100 }, { maxWidth: 10 })).toBe(false);
14
- });
15
- test("minWidth and maxWidth", () => {
16
- expect(equalMedia({ maxWidth: 100, minWidth: 10 }, { maxWidth: 100 })).toBe(
17
- false
18
- );
19
- expect(equalMedia({ maxWidth: 100, minWidth: 10 }, { minWidth: 10 })).toBe(
20
- false
21
- );
22
- expect(
23
- equalMedia(
24
- { maxWidth: 100, minWidth: 10 },
25
- { maxWidth: 100, minWidth: 10 }
26
- )
27
- ).toBe(true);
28
- });
29
- });
@@ -1,11 +0,0 @@
1
- "use strict";
2
- import { compareMedia } from "./compare-media";
3
- import { matchMedia } from "./match-media";
4
- export const findApplicableMedia = (media, width) => {
5
- const sortedMedia = [...media].sort(compareMedia).reverse();
6
- for (const options of sortedMedia) {
7
- if (matchMedia(options, width)) {
8
- return options;
9
- }
10
- }
11
- };
@@ -1,42 +0,0 @@
1
- "use strict";
2
- import { describe, test, expect } from "@jest/globals";
3
- import { findApplicableMedia } from "./find-applicable-media";
4
- const media = [
5
- {},
6
- { maxWidth: 991 },
7
- { maxWidth: 767 },
8
- { maxWidth: 479 },
9
- { minWidth: 1280 },
10
- { minWidth: 1440 },
11
- { minWidth: 1920 }
12
- ];
13
- describe("Find applicable media", () => {
14
- test("200", () => {
15
- expect(findApplicableMedia([...media], 200)).toStrictEqual({
16
- maxWidth: 479
17
- });
18
- });
19
- test("479", () => {
20
- expect(findApplicableMedia([...media], 479)).toStrictEqual({
21
- maxWidth: 479
22
- });
23
- });
24
- test("480", () => {
25
- expect(findApplicableMedia([...media], 480)).toStrictEqual({
26
- maxWidth: 767
27
- });
28
- });
29
- test("1279", () => {
30
- expect(findApplicableMedia([...media], 1279)).toStrictEqual({});
31
- });
32
- test("1280", () => {
33
- expect(findApplicableMedia([...media], 1280)).toStrictEqual({
34
- minWidth: 1280
35
- });
36
- });
37
- test("1440", () => {
38
- expect(findApplicableMedia([...media], 1440)).toStrictEqual({
39
- minWidth: 1440
40
- });
41
- });
42
- });
package/lib/core/index.js DELETED
@@ -1,9 +0,0 @@
1
- "use strict";
2
- export { CssEngine } from "./css-engine";
3
- export * from "./create-css-engine";
4
- export * from "./to-value";
5
- export * from "./to-property";
6
- export * from "./match-media";
7
- export * from "./equal-media";
8
- export * from "./compare-media";
9
- export * from "./find-applicable-media";
@@ -1,6 +0,0 @@
1
- "use strict";
2
- export const matchMedia = (options, width) => {
3
- const minWidth = options.minWidth ?? Number.MIN_SAFE_INTEGER;
4
- const maxWidth = options.maxWidth ?? Number.MAX_SAFE_INTEGER;
5
- return width >= minWidth && width <= maxWidth;
6
- };