@webstudio-is/css-engine 0.145.0 → 0.163.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 CHANGED
@@ -96,28 +96,150 @@ var toProperty = (property) => {
96
96
  };
97
97
 
98
98
  // src/core/rules.ts
99
+ var getDeclarationKey = (declaraionKey) => {
100
+ const { breakpoint, selector, property } = declaraionKey;
101
+ return `${breakpoint}:${selector}:${property}`;
102
+ };
103
+ var MixinRule = class {
104
+ // use map to avoid duplicated properties
105
+ #declarations = /* @__PURE__ */ new Map();
106
+ #dirtyBreakpoints = /* @__PURE__ */ new Set();
107
+ /*
108
+ * check if breakpoint was updated
109
+ */
110
+ isDirtyBreakpoint(breakpoint) {
111
+ return this.#dirtyBreakpoints.has(breakpoint);
112
+ }
113
+ /**
114
+ * reset breakpoints invalidation
115
+ */
116
+ clearBreakpoints() {
117
+ this.#dirtyBreakpoints.clear();
118
+ }
119
+ setDeclaration(declaration) {
120
+ this.#declarations.set(getDeclarationKey(declaration), declaration);
121
+ this.#dirtyBreakpoints.add(declaration.breakpoint);
122
+ }
123
+ deleteDeclaration(declaration) {
124
+ this.#declarations.delete(getDeclarationKey(declaration));
125
+ this.#dirtyBreakpoints.add(declaration.breakpoint);
126
+ }
127
+ getDeclarations() {
128
+ return this.#declarations.values();
129
+ }
130
+ };
131
+ var NestingRule = class {
132
+ #selector;
133
+ #descendantSuffix;
134
+ #mixinRules = /* @__PURE__ */ new Map();
135
+ #mixins = /* @__PURE__ */ new Set();
136
+ // use map to avoid duplicated properties
137
+ #declarations = /* @__PURE__ */ new Map();
138
+ // cached generated rule by breakpoint
139
+ #cache = /* @__PURE__ */ new Map();
140
+ constructor(mixinRules, selector, descendantSuffix) {
141
+ this.#selector = selector;
142
+ this.#descendantSuffix = descendantSuffix;
143
+ this.#mixinRules = mixinRules;
144
+ }
145
+ getSelector() {
146
+ return this.#selector;
147
+ }
148
+ setSelector(selector) {
149
+ this.#selector = selector;
150
+ this.#cache.clear();
151
+ }
152
+ getDescendantSuffix() {
153
+ return this.#descendantSuffix;
154
+ }
155
+ addMixin(mixin) {
156
+ this.#mixins.add(mixin);
157
+ this.#cache.clear();
158
+ }
159
+ applyMixins(mixins) {
160
+ this.#mixins = new Set(mixins);
161
+ this.#cache.clear();
162
+ }
163
+ setDeclaration(declaration) {
164
+ this.#declarations.set(getDeclarationKey(declaration), declaration);
165
+ this.#cache.delete(declaration.breakpoint);
166
+ }
167
+ deleteDeclaration(declaration) {
168
+ this.#declarations.delete(getDeclarationKey(declaration));
169
+ this.#cache.delete(declaration.breakpoint);
170
+ }
171
+ getDeclarations() {
172
+ const declarations = /* @__PURE__ */ new Map();
173
+ for (const mixin of this.#mixins) {
174
+ const rule = this.#mixinRules.get(mixin);
175
+ if (rule === void 0) {
176
+ continue;
177
+ }
178
+ for (const declaration of rule.getDeclarations()) {
179
+ declarations.set(getDeclarationKey(declaration), declaration);
180
+ }
181
+ }
182
+ for (const declaration of this.#declarations.values()) {
183
+ declarations.set(getDeclarationKey(declaration), declaration);
184
+ }
185
+ return declarations.values();
186
+ }
187
+ toString({
188
+ breakpoint,
189
+ indent = 0,
190
+ transformValue
191
+ }) {
192
+ for (const mixin of this.#mixins) {
193
+ const rule = this.#mixinRules.get(mixin);
194
+ if (rule?.isDirtyBreakpoint(breakpoint)) {
195
+ this.#cache.delete(breakpoint);
196
+ }
197
+ }
198
+ const cached = this.#cache.get(breakpoint);
199
+ if (cached && cached.indent === indent && cached.transformValue === transformValue) {
200
+ return cached.generated;
201
+ }
202
+ const spaces = " ".repeat(indent);
203
+ const linesBySelector = /* @__PURE__ */ new Map();
204
+ for (const declaration of this.getDeclarations()) {
205
+ if (declaration.breakpoint !== breakpoint) {
206
+ continue;
207
+ }
208
+ const { selector: nestedSelector, property, value } = declaration;
209
+ const selector = this.#selector + this.#descendantSuffix + nestedSelector;
210
+ const lines = linesBySelector.get(selector) ?? "";
211
+ const propertyString = toProperty(property);
212
+ const valueString = toValue(value, transformValue);
213
+ const line = `${spaces} ${propertyString}: ${valueString}`;
214
+ linesBySelector.set(selector, lines === "" ? line : `${lines};
215
+ ${line}`);
216
+ }
217
+ const generated = Array.from(linesBySelector).sort(
218
+ ([leftSelector], [rightSelector]) => leftSelector.localeCompare(rightSelector)
219
+ ).map(
220
+ ([selector, lines]) => `${spaces}${selector} {
221
+ ${lines}
222
+ ${spaces}}
223
+ `
224
+ ).join("").trimEnd();
225
+ this.#cache.set(breakpoint, { generated, indent, transformValue });
226
+ return generated;
227
+ }
228
+ };
99
229
  var StylePropertyMap = class {
230
+ #cached;
100
231
  #styleMap = /* @__PURE__ */ new Map();
101
- #isDirty = true;
102
- #string = "";
103
232
  #indent = 0;
104
233
  #transformValue;
105
- #onChange;
106
- constructor(style, transformValue, onChange) {
107
- this.#transformValue = transformValue;
108
- this.#onChange = onChange;
234
+ constructor(style) {
109
235
  let property;
110
236
  for (property in style) {
111
237
  this.#styleMap.set(property, style[property]);
112
238
  }
113
239
  }
114
- setTransformer(transformValue) {
115
- this.#transformValue = transformValue;
116
- }
117
240
  set(property, value) {
118
241
  this.#styleMap.set(property, value);
119
- this.#isDirty = true;
120
- this.#onChange?.();
242
+ this.#cached = void 0;
121
243
  }
122
244
  get(property) {
123
245
  return this.#styleMap.get(property);
@@ -133,19 +255,19 @@ var StylePropertyMap = class {
133
255
  }
134
256
  delete(property) {
135
257
  this.#styleMap.delete(property);
136
- this.#isDirty = true;
137
- this.#onChange?.();
258
+ this.#cached = void 0;
138
259
  }
139
260
  clear() {
140
261
  this.#styleMap.clear();
141
- this.#isDirty = true;
142
- this.#onChange?.();
143
- }
144
- toString({ indent = 0 } = {}) {
145
- if (this.#isDirty === false && indent === this.#indent) {
146
- return this.#string;
262
+ this.#cached = void 0;
263
+ }
264
+ toString({
265
+ indent = 0,
266
+ transformValue
267
+ } = {}) {
268
+ if (this.#cached && indent === this.#indent && transformValue === this.#transformValue) {
269
+ return this.#cached;
147
270
  }
148
- this.#indent = indent;
149
271
  const block = [];
150
272
  const spaces = " ".repeat(indent);
151
273
  for (const [property, value] of this.#styleMap) {
@@ -153,48 +275,53 @@ var StylePropertyMap = class {
153
275
  continue;
154
276
  }
155
277
  block.push(
156
- `${spaces}${toProperty(property)}: ${toValue(
157
- value,
158
- this.#transformValue
159
- )}`
278
+ `${spaces}${toProperty(property)}: ${toValue(value, transformValue)}`
160
279
  );
161
280
  }
162
- this.#string = block.join(";\n");
163
- this.#isDirty = false;
164
- return this.#string;
281
+ this.#cached = block.join(";\n");
282
+ this.#indent = indent;
283
+ this.#transformValue = transformValue;
284
+ return this.#cached;
165
285
  }
166
286
  };
167
287
  var StyleRule = class {
168
288
  styleMap;
169
289
  selectorText;
170
- constructor(selectorText, style, transformValue, onChange) {
290
+ constructor(selectorText, style) {
171
291
  this.selectorText = selectorText;
172
- this.styleMap = style instanceof StylePropertyMap ? style : new StylePropertyMap(style, transformValue, onChange);
292
+ this.styleMap = style instanceof StylePropertyMap ? style : new StylePropertyMap(style);
173
293
  }
174
294
  get cssText() {
175
295
  return this.toString();
176
296
  }
177
- toString(options = { indent: 0 }) {
178
- const spaces = " ".repeat(options.indent);
297
+ toString({
298
+ indent = 0,
299
+ transformValue
300
+ } = {}) {
301
+ const spaces = " ".repeat(indent);
302
+ const content = this.styleMap.toString({
303
+ indent: indent + 2,
304
+ transformValue
305
+ });
179
306
  return `${spaces}${this.selectorText} {
180
- ${this.styleMap.toString({
181
- indent: options.indent + 2
182
- })}
307
+ ${content}
183
308
  ${spaces}}`;
184
309
  }
185
310
  };
186
311
  var MediaRule = class {
312
+ #name;
187
313
  options;
188
314
  rules;
189
315
  #mediaType;
190
- constructor(options = {}) {
316
+ constructor(name, options = {}) {
317
+ this.#name = name;
191
318
  this.options = options;
192
319
  this.rules = /* @__PURE__ */ new Map();
193
320
  this.#mediaType = options.mediaType ?? "all";
194
321
  }
195
322
  insertRule(rule) {
196
323
  this.rules.set(
197
- "selectorText" in rule ? rule.selectorText : rule.cssText,
324
+ rule instanceof StyleRule ? rule.selectorText : rule.cssText,
198
325
  rule
199
326
  );
200
327
  return rule;
@@ -203,12 +330,31 @@ var MediaRule = class {
203
330
  return this.toString();
204
331
  }
205
332
  toString() {
206
- if (this.rules.size === 0) {
333
+ return this.generateRule({ nestingRules: [] });
334
+ }
335
+ generateRule({
336
+ nestingRules,
337
+ transformValue
338
+ }) {
339
+ if (this.rules.size === 0 && nestingRules.length === 0) {
207
340
  return "";
208
341
  }
209
342
  const rules = [];
210
343
  for (const rule of this.rules.values()) {
211
- rules.push(rule.toString({ indent: 2 }));
344
+ rules.push(rule.toString({ indent: 2, transformValue }));
345
+ }
346
+ for (const rule of nestingRules) {
347
+ const generatedRule = rule.toString({
348
+ breakpoint: this.#name,
349
+ indent: 2,
350
+ transformValue
351
+ });
352
+ if (generatedRule !== "") {
353
+ rules.push(generatedRule);
354
+ }
355
+ }
356
+ if (rules.length === 0) {
357
+ return "";
212
358
  }
213
359
  let conditionText = "";
214
360
  const { minWidth, maxWidth } = this.options;
@@ -227,26 +373,28 @@ ${rules.join(
227
373
  };
228
374
  var PlaintextRule = class {
229
375
  cssText;
230
- styleMap;
231
376
  constructor(cssText) {
232
377
  this.cssText = cssText;
233
- this.styleMap = new StylePropertyMap({});
234
378
  }
235
379
  toString() {
236
380
  return this.cssText;
237
381
  }
238
382
  };
239
383
  var FontFaceRule = class {
240
- options;
384
+ #cached;
385
+ #options;
241
386
  constructor(options) {
242
- this.options = options;
387
+ this.#options = options;
243
388
  }
244
389
  get cssText() {
245
390
  return this.toString();
246
391
  }
247
392
  toString() {
393
+ if (this.#cached) {
394
+ return this.#cached;
395
+ }
248
396
  const decls = [];
249
- const { fontFamily, fontStyle, fontWeight, fontDisplay, src } = this.options;
397
+ const { fontFamily, fontStyle, fontWeight, fontDisplay, src } = this.#options;
250
398
  const value = toValue(
251
399
  { type: "fontFamily", value: [fontFamily] },
252
400
  // Avoids adding a fallback automatically which needs to happen for font family in general but not for font face.
@@ -257,9 +405,10 @@ var FontFaceRule = class {
257
405
  decls.push(`font-weight: ${fontWeight}`);
258
406
  decls.push(`font-display: ${fontDisplay}`);
259
407
  decls.push(`src: ${src}`);
260
- return `@font-face {
408
+ this.#cached = `@font-face {
261
409
  ${decls.join("; ")};
262
410
  }`;
411
+ return this.#cached;
263
412
  }
264
413
  };
265
414
 
@@ -325,23 +474,26 @@ var StyleSheet = class {
325
474
  #cssText = "";
326
475
  #mediaRules = /* @__PURE__ */ new Map();
327
476
  #plainRules = /* @__PURE__ */ new Map();
477
+ #mixinRules = /* @__PURE__ */ new Map();
478
+ nestingRules = /* @__PURE__ */ new Map();
328
479
  #fontFaceRules = [];
329
- #isDirty = false;
480
+ #transformValue;
330
481
  #element;
331
482
  constructor(element) {
332
483
  this.#element = element;
333
484
  }
485
+ setTransformer(transformValue) {
486
+ this.#transformValue = transformValue;
487
+ }
334
488
  addMediaRule(id, options) {
335
489
  let mediaRule = this.#mediaRules.get(id);
336
490
  if (mediaRule === void 0) {
337
- mediaRule = new MediaRule(options);
491
+ mediaRule = new MediaRule(id, options);
338
492
  this.#mediaRules.set(id, mediaRule);
339
- this.#isDirty = true;
340
493
  return mediaRule;
341
494
  }
342
495
  if (options) {
343
496
  mediaRule.options = options;
344
- this.#isDirty = true;
345
497
  }
346
498
  if (mediaRule === void 0) {
347
499
  throw new Error("No media rule found");
@@ -353,21 +505,32 @@ var StyleSheet = class {
353
505
  if (rule !== void 0) {
354
506
  return rule;
355
507
  }
356
- this.#isDirty = true;
357
508
  return this.#plainRules.set(cssText, new PlaintextRule(cssText));
358
509
  }
510
+ addMixinRule(name) {
511
+ let rule = this.#mixinRules.get(name);
512
+ if (rule === void 0) {
513
+ rule = new MixinRule();
514
+ this.#mixinRules.set(name, rule);
515
+ }
516
+ return rule;
517
+ }
518
+ addNestingRule(selector, descendantSuffix = "") {
519
+ const key = selector + descendantSuffix;
520
+ let rule = this.nestingRules.get(key);
521
+ if (rule === void 0) {
522
+ rule = new NestingRule(this.#mixinRules, selector, descendantSuffix);
523
+ this.nestingRules.set(key, rule);
524
+ }
525
+ return rule;
526
+ }
359
527
  addFontFaceRule(options) {
360
- this.#isDirty = true;
361
528
  return this.#fontFaceRules.push(new FontFaceRule(options));
362
529
  }
363
- markAsDirty() {
364
- this.#isDirty = true;
365
- }
366
- get cssText() {
367
- if (this.#isDirty === false) {
368
- return this.#cssText;
369
- }
370
- this.#isDirty = false;
530
+ generateWith({
531
+ nestingRules,
532
+ transformValue
533
+ }) {
371
534
  const css = [];
372
535
  css.push(...this.#fontFaceRules.map((rule) => rule.cssText));
373
536
  for (const plaintextRule of this.#plainRules.values()) {
@@ -377,19 +540,32 @@ var StyleSheet = class {
377
540
  (ruleA, ruleB) => compareMedia(ruleA.options, ruleB.options)
378
541
  );
379
542
  for (const mediaRule of sortedMediaRules) {
380
- const { cssText } = mediaRule;
543
+ const cssText = mediaRule.generateRule({
544
+ nestingRules,
545
+ transformValue
546
+ });
381
547
  if (cssText !== "") {
382
548
  css.push(cssText);
383
549
  }
384
550
  }
551
+ for (const rule of this.#mixinRules.values()) {
552
+ rule.clearBreakpoints();
553
+ }
385
554
  this.#cssText = css.join("\n");
386
555
  return this.#cssText;
387
556
  }
557
+ get cssText() {
558
+ return this.generateWith({
559
+ nestingRules: Array.from(this.nestingRules.values()),
560
+ transformValue: this.#transformValue
561
+ });
562
+ }
388
563
  clear() {
389
564
  this.#mediaRules.clear();
565
+ this.#mixinRules.clear();
566
+ this.nestingRules.clear();
390
567
  this.#plainRules.clear();
391
568
  this.#fontFaceRules = [];
392
- this.#isDirty = true;
393
569
  }
394
570
  render() {
395
571
  this.#element.mount();
@@ -409,58 +585,12 @@ var StyleSheet = class {
409
585
  // src/core/style-sheet-regular.ts
410
586
  var defaultMediaRuleId = "__default-media-rule__";
411
587
  var StyleSheetRegular = class extends StyleSheet {
412
- addStyleRule(rule, selectorText, transformValue) {
588
+ addStyleRule(rule, selectorText) {
413
589
  const mediaRule = this.addMediaRule(rule.breakpoint || defaultMediaRuleId);
414
- this.markAsDirty();
415
- const styleRule = new StyleRule(
416
- selectorText,
417
- rule.style,
418
- transformValue,
419
- this.#onChangeRule
420
- );
590
+ const styleRule = new StyleRule(selectorText, rule.style);
421
591
  mediaRule.insertRule(styleRule);
422
592
  return styleRule;
423
593
  }
424
- #onChangeRule = () => {
425
- this.markAsDirty();
426
- };
427
- };
428
-
429
- // src/core/style-sheet-atomic.ts
430
- import hash from "@emotion/hash";
431
- var defaultMediaRuleId2 = "__default-media-rule__";
432
- var StyleSheetAtomic = class extends StyleSheet {
433
- addStyleRule({ style, breakpoint }, selectorSuffix = "", transformValue) {
434
- const mediaRule = this.addMediaRule(breakpoint || defaultMediaRuleId2);
435
- const styleRules = [];
436
- const classes = [];
437
- let property;
438
- for (property in style) {
439
- const stylePropertyMap = new StylePropertyMap(
440
- { [property]: style[property] },
441
- transformValue
442
- );
443
- const className = `c${hash(
444
- stylePropertyMap + selectorSuffix + breakpoint
445
- )}`;
446
- classes.push(className);
447
- const newStyleRule = new StyleRule(
448
- `.${className}${selectorSuffix}`,
449
- stylePropertyMap,
450
- transformValue,
451
- this.#onChangeRule
452
- );
453
- styleRules.push(newStyleRule);
454
- if (mediaRule.rules.has(newStyleRule.selectorText) === false) {
455
- mediaRule.insertRule(newStyleRule);
456
- this.markAsDirty();
457
- }
458
- }
459
- return { styleRules, classes };
460
- }
461
- #onChangeRule = () => {
462
- this.markAsDirty();
463
- };
464
594
  };
465
595
 
466
596
  // src/core/create-style-sheet.ts
@@ -468,10 +598,6 @@ var createRegularStyleSheet = (options) => {
468
598
  const element = new StyleElement(options?.name);
469
599
  return new StyleSheetRegular(element);
470
600
  };
471
- var createAtomicStyleSheet = (options) => {
472
- const element = new StyleElement(options?.name);
473
- return new StyleSheetAtomic(element);
474
- };
475
601
 
476
602
  // src/core/match-media.ts
477
603
  var matchMedia = (options, width) => {
@@ -495,6 +621,45 @@ var findApplicableMedia = (media, width) => {
495
621
  }
496
622
  };
497
623
 
624
+ // src/core/atomic.ts
625
+ import hash from "@emotion/hash";
626
+ var generateAtomic = (sheet, options) => {
627
+ const { getKey, transformValue } = options;
628
+ const atomicRules = /* @__PURE__ */ new Map();
629
+ const classesMap = /* @__PURE__ */ new Map();
630
+ for (const rule of sheet.nestingRules.values()) {
631
+ const descendantSuffix = rule.getDescendantSuffix();
632
+ const groupKey = getKey(rule);
633
+ let classList = classesMap.get(groupKey);
634
+ if (classList === void 0) {
635
+ classList = [];
636
+ classesMap.set(groupKey, classList);
637
+ }
638
+ for (const declaration of rule.getDeclarations()) {
639
+ const atomicHash = hash(
640
+ descendantSuffix + declaration.breakpoint + declaration.selector + declaration.property + toValue(declaration.value, transformValue)
641
+ );
642
+ const className = `c${atomicHash}`;
643
+ let atomicRule = atomicRules.get(atomicHash);
644
+ if (atomicRule === void 0) {
645
+ atomicRule = new NestingRule(
646
+ /* @__PURE__ */ new Map(),
647
+ `.${className}`,
648
+ descendantSuffix
649
+ );
650
+ atomicRule.setDeclaration(declaration);
651
+ atomicRules.set(atomicHash, atomicRule);
652
+ }
653
+ classList.push(className);
654
+ }
655
+ }
656
+ const cssText = sheet.generateWith({
657
+ nestingRules: Array.from(atomicRules.values()),
658
+ transformValue
659
+ });
660
+ return { cssText, classesMap };
661
+ };
662
+
498
663
  // src/schema.ts
499
664
  import { z } from "zod";
500
665
  var Unit = z.string();
@@ -618,10 +783,10 @@ export {
618
783
  UnitValue,
619
784
  UnparsedValue,
620
785
  compareMedia,
621
- createAtomicStyleSheet,
622
786
  createRegularStyleSheet,
623
787
  equalMedia,
624
788
  findApplicableMedia,
789
+ generateAtomic,
625
790
  isValidStaticStyleValue,
626
791
  matchMedia,
627
792
  toProperty,
@@ -0,0 +1,12 @@
1
+ import type { StyleSheet } from "./style-sheet";
2
+ import { NestingRule } from "./rules";
3
+ import { type TransformValue } from "./to-value";
4
+ type Options = {
5
+ getKey: (rule: NestingRule) => string;
6
+ transformValue?: TransformValue;
7
+ };
8
+ export declare const generateAtomic: (sheet: StyleSheet, options: Options) => {
9
+ cssText: string;
10
+ classesMap: Map<string, string[]>;
11
+ };
12
+ export {};
@@ -1,8 +1,4 @@
1
1
  import { StyleSheetRegular } from "./style-sheet-regular";
2
- import { StyleSheetAtomic } from "./style-sheet-atomic";
3
2
  export declare const createRegularStyleSheet: (options?: {
4
3
  name?: string;
5
4
  }) => StyleSheetRegular;
6
- export declare const createAtomicStyleSheet: (options?: {
7
- name?: string;
8
- }) => StyleSheetAtomic;
@@ -1,6 +1,5 @@
1
- export type { AnyRule, StyleRule, MediaRule, PlaintextRule, FontFaceRule, } from "./rules";
1
+ export type { NestingRule, StyleRule, MediaRule, PlaintextRule, FontFaceRule, } from "./rules";
2
2
  export type { StyleSheetRegular } from "./style-sheet-regular";
3
- export type { StyleSheetAtomic } from "./style-sheet-atomic";
4
3
  export * from "./create-style-sheet";
5
4
  export * from "./to-value";
6
5
  export * from "./to-property";
@@ -8,3 +7,4 @@ export * from "./match-media";
8
7
  export * from "./equal-media";
9
8
  export * from "./compare-media";
10
9
  export * from "./find-applicable-media";
10
+ export * from "./atomic";
@@ -1,9 +1,67 @@
1
1
  import type { Style, StyleProperty, StyleValue } from "../schema";
2
2
  import { type TransformValue } from "./to-value";
3
+ type Declaration = {
4
+ breakpoint: string;
5
+ selector: string;
6
+ property: StyleProperty;
7
+ value: StyleValue;
8
+ };
9
+ type DeclarationKey = Omit<Declaration, "value">;
10
+ /**
11
+ * Reusable style rule in any nesting rule
12
+ *
13
+ * @mixin name {
14
+ * \@media breakpoint {
15
+ * &selector {
16
+ * property: value
17
+ * }
18
+ * }
19
+ * }
20
+ */
21
+ export declare class MixinRule {
22
+ #private;
23
+ isDirtyBreakpoint(breakpoint: string): boolean;
24
+ /**
25
+ * reset breakpoints invalidation
26
+ */
27
+ clearBreakpoints(): void;
28
+ setDeclaration(declaration: Declaration): void;
29
+ deleteDeclaration(declaration: DeclarationKey): void;
30
+ getDeclarations(): IterableIterator<Declaration>;
31
+ }
32
+ /**
33
+ * Universal style rule with nested selectors and media queries support
34
+ * Rules are generated by each media query
35
+ * and heavily cached to avoid complex computation
36
+ *
37
+ * selector {
38
+ * \@media breakpoint {
39
+ * &selector {
40
+ * property: value
41
+ * }
42
+ * }
43
+ * }
44
+ */
45
+ export declare class NestingRule {
46
+ #private;
47
+ constructor(mixinRules: Map<string, MixinRule>, selector: string, descendantSuffix: string);
48
+ getSelector(): string;
49
+ setSelector(selector: string): void;
50
+ getDescendantSuffix(): string;
51
+ addMixin(mixin: string): void;
52
+ applyMixins(mixins: string[]): void;
53
+ setDeclaration(declaration: Declaration): void;
54
+ deleteDeclaration(declaration: DeclarationKey): void;
55
+ getDeclarations(): IterableIterator<Declaration>;
56
+ toString({ breakpoint, indent, transformValue, }: {
57
+ breakpoint: string;
58
+ indent?: number;
59
+ transformValue?: TransformValue;
60
+ }): string;
61
+ }
3
62
  export declare class StylePropertyMap {
4
63
  #private;
5
- constructor(style: Style, transformValue?: TransformValue, onChange?: () => void);
6
- setTransformer(transformValue: TransformValue): void;
64
+ constructor(style: Style);
7
65
  set(property: StyleProperty, value?: StyleValue): void;
8
66
  get(property: StyleProperty): {
9
67
  type: "unit";
@@ -1024,17 +1082,19 @@ export declare class StylePropertyMap {
1024
1082
  keys(): IterableIterator<StyleProperty>;
1025
1083
  delete(property: StyleProperty): void;
1026
1084
  clear(): void;
1027
- toString({ indent }?: {
1028
- indent?: number | undefined;
1085
+ toString({ indent, transformValue, }?: {
1086
+ indent?: number;
1087
+ transformValue?: TransformValue;
1029
1088
  }): string;
1030
1089
  }
1031
1090
  export declare class StyleRule {
1032
1091
  styleMap: StylePropertyMap;
1033
1092
  selectorText: string;
1034
- constructor(selectorText: string, style: StylePropertyMap | Style, transformValue?: TransformValue, onChange?: () => void);
1093
+ constructor(selectorText: string, style: StylePropertyMap | Style);
1035
1094
  get cssText(): string;
1036
- toString(options?: {
1037
- indent: number;
1095
+ toString({ indent, transformValue, }?: {
1096
+ indent?: number;
1097
+ transformValue?: TransformValue;
1038
1098
  }): string;
1039
1099
  }
1040
1100
  export type MediaRuleOptions = {
@@ -1046,14 +1106,17 @@ export declare class MediaRule {
1046
1106
  #private;
1047
1107
  options: MediaRuleOptions;
1048
1108
  rules: Map<string, StyleRule | PlaintextRule>;
1049
- constructor(options?: MediaRuleOptions);
1109
+ constructor(name: string, options?: MediaRuleOptions);
1050
1110
  insertRule(rule: StyleRule | PlaintextRule): StyleRule | PlaintextRule;
1051
1111
  get cssText(): string;
1052
1112
  toString(): string;
1113
+ generateRule({ nestingRules, transformValue, }: {
1114
+ nestingRules: NestingRule[];
1115
+ transformValue?: TransformValue;
1116
+ }): string;
1053
1117
  }
1054
1118
  export declare class PlaintextRule {
1055
1119
  cssText: string;
1056
- styleMap: StylePropertyMap;
1057
1120
  constructor(cssText: string);
1058
1121
  toString(): string;
1059
1122
  }
@@ -1065,9 +1128,9 @@ export type FontFaceOptions = {
1065
1128
  src: string;
1066
1129
  };
1067
1130
  export declare class FontFaceRule {
1068
- options: FontFaceOptions;
1131
+ #private;
1069
1132
  constructor(options: FontFaceOptions);
1070
1133
  get cssText(): string;
1071
1134
  toString(): string;
1072
1135
  }
1073
- export type AnyRule = StyleRule | MediaRule | PlaintextRule | FontFaceRule;
1136
+ export {};
@@ -1,7 +1,5 @@
1
1
  import { StyleRule } from "./rules";
2
- import type { TransformValue } from "./to-value";
3
2
  import { StyleSheet, type CssRule } from "./style-sheet";
4
3
  export declare class StyleSheetRegular extends StyleSheet {
5
- #private;
6
- addStyleRule(rule: CssRule, selectorText: string, transformValue?: TransformValue): StyleRule;
4
+ addStyleRule(rule: CssRule, selectorText: string): StyleRule;
7
5
  }
@@ -1,17 +1,25 @@
1
1
  import type { Style } from "../schema";
2
- import { MediaRule, PlaintextRule, type FontFaceOptions, type MediaRuleOptions } from "./rules";
2
+ import { MediaRule, MixinRule, NestingRule, PlaintextRule, type FontFaceOptions, type MediaRuleOptions } from "./rules";
3
3
  import { StyleElement } from "./style-element";
4
+ import type { TransformValue } from "./to-value";
4
5
  export type CssRule = {
5
6
  style: Style;
6
7
  breakpoint?: string;
7
8
  };
8
9
  export declare class StyleSheet {
9
10
  #private;
11
+ nestingRules: Map<string, NestingRule>;
10
12
  constructor(element: StyleElement);
13
+ setTransformer(transformValue: TransformValue): void;
11
14
  addMediaRule(id: string, options?: MediaRuleOptions): MediaRule;
12
15
  addPlaintextRule(cssText: string): PlaintextRule | Map<string, PlaintextRule>;
16
+ addMixinRule(name: string): MixinRule;
17
+ addNestingRule(selector: string, descendantSuffix?: string): NestingRule;
13
18
  addFontFaceRule(options: FontFaceOptions): number;
14
- markAsDirty(): void;
19
+ generateWith({ nestingRules, transformValue, }: {
20
+ nestingRules: NestingRule[];
21
+ transformValue?: TransformValue;
22
+ }): string;
15
23
  get cssText(): string;
16
24
  clear(): void;
17
25
  render(): void;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@webstudio-is/css-engine",
3
- "version": "0.145.0",
3
+ "version": "0.163.0",
4
4
  "description": "CSS Renderer for Webstudio",
5
5
  "author": "Webstudio <github@webstudio.is>",
6
6
  "homepage": "https://webstudio.is",
@@ -9,14 +9,14 @@
9
9
  "@emotion/hash": "^0.9.1",
10
10
  "hyphenate-style-name": "^1.0.4",
11
11
  "zod": "^3.22.4",
12
- "@webstudio-is/fonts": "0.145.0",
13
- "@webstudio-is/error-utils": "0.145.0"
12
+ "@webstudio-is/fonts": "0.163.0",
13
+ "@webstudio-is/error-utils": "0.163.0"
14
14
  },
15
15
  "devDependencies": {
16
16
  "@jest/globals": "^29.7.0",
17
- "@storybook/addon-essentials": "^7.4.0",
18
- "@storybook/addon-links": "^7.4.0",
19
- "@storybook/react": "^7.4.0",
17
+ "@storybook/addon-essentials": "^8.1.2",
18
+ "@storybook/addon-links": "^8.1.2",
19
+ "@storybook/react": "^8.1.2",
20
20
  "@types/hyphenate-style-name": "^1.0.0",
21
21
  "@types/react": "^18.2.70",
22
22
  "@types/react-dom": "^18.2.25",
@@ -30,8 +30,7 @@
30
30
  "exports": {
31
31
  "webstudio": "./src/index.ts",
32
32
  "types": "./lib/types/index.d.ts",
33
- "import": "./lib/index.js",
34
- "require": "./lib/index.js"
33
+ "import": "./lib/index.js"
35
34
  },
36
35
  "files": [
37
36
  "lib/*",
@@ -1,10 +0,0 @@
1
- import { StyleRule } from "./rules";
2
- import type { TransformValue } from "./to-value";
3
- import { StyleSheet, type CssRule } from "./style-sheet";
4
- export declare class StyleSheetAtomic extends StyleSheet {
5
- #private;
6
- addStyleRule({ style, breakpoint }: CssRule, selectorSuffix?: string, transformValue?: TransformValue): {
7
- styleRules: StyleRule[];
8
- classes: string[];
9
- };
10
- }