@webstudio-is/css-engine 0.50.0 → 0.52.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/cjs/core/compare-media.js +32 -0
- package/lib/cjs/core/{css-engine.cjs → css-engine.js} +5 -1
- package/lib/cjs/core/equal-media.js +26 -0
- package/lib/cjs/core/{index.cjs → index.js} +3 -0
- package/lib/cjs/core/match-media.js +28 -0
- package/lib/cjs/core/{rules.cjs → rules.js} +3 -14
- package/lib/cjs/package.json +1 -0
- package/lib/core/compare-media.js +12 -0
- package/lib/core/css-engine.js +5 -1
- package/lib/core/equal-media.js +6 -0
- package/lib/core/index.js +3 -0
- package/lib/core/match-media.js +8 -0
- package/lib/core/rules.js +3 -14
- package/lib/types/core/compare-media.d.ts +6 -0
- package/lib/types/core/compare-media.test.d.ts +1 -0
- package/lib/types/core/create-css-engine.d.ts +2 -0
- package/lib/types/core/css-engine.d.ts +19 -0
- package/lib/types/core/css-engine.stories.d.ts +6 -0
- package/lib/types/core/css-engine.test.d.ts +1 -0
- package/lib/types/core/equal-media.d.ts +2 -0
- package/lib/types/core/equal-media.test.d.ts +1 -0
- package/lib/types/core/index.d.ts +7 -0
- package/lib/types/core/match-media.d.ts +2 -0
- package/lib/types/core/match-media.test.d.ts +1 -0
- package/lib/types/core/rules.d.ts +51 -0
- package/lib/types/core/style-element.d.ts +10 -0
- package/lib/types/core/style-sheet.d.ts +6 -0
- package/lib/types/core/to-property.d.ts +2 -0
- package/lib/types/core/to-property.test.d.ts +1 -0
- package/lib/types/core/to-value.d.ts +6 -0
- package/lib/types/core/to-value.test.d.ts +1 -0
- package/lib/types/index.d.ts +1 -0
- package/package.json +6 -5
- package/src/core/compare-media.test.ts +40 -0
- package/src/core/compare-media.ts +18 -0
- package/src/core/css-engine.test.ts +39 -0
- package/src/core/css-engine.ts +6 -1
- package/src/core/equal-media.test.ts +31 -0
- package/src/core/equal-media.ts +5 -0
- package/src/core/index.ts +3 -0
- package/src/core/match-media.test.ts +24 -0
- package/src/core/match-media.ts +7 -0
- package/src/core/rules.ts +3 -17
- /package/lib/cjs/core/{create-css-engine.cjs → create-css-engine.js} +0 -0
- /package/lib/cjs/core/{style-element.cjs → style-element.js} +0 -0
- /package/lib/cjs/core/{style-sheet.cjs → style-sheet.js} +0 -0
- /package/lib/cjs/core/{to-property.cjs → to-property.js} +0 -0
- /package/lib/cjs/core/{to-value.cjs → to-value.js} +0 -0
- /package/lib/cjs/{index.cjs → index.js} +0 -0
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __defProp = Object.defineProperty;
|
|
3
|
+
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
4
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
5
|
+
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
6
|
+
var __export = (target, all) => {
|
|
7
|
+
for (var name in all)
|
|
8
|
+
__defProp(target, name, { get: all[name], enumerable: true });
|
|
9
|
+
};
|
|
10
|
+
var __copyProps = (to, from, except, desc) => {
|
|
11
|
+
if (from && typeof from === "object" || typeof from === "function") {
|
|
12
|
+
for (let key of __getOwnPropNames(from))
|
|
13
|
+
if (!__hasOwnProp.call(to, key) && key !== except)
|
|
14
|
+
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
|
15
|
+
}
|
|
16
|
+
return to;
|
|
17
|
+
};
|
|
18
|
+
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
19
|
+
var compare_media_exports = {};
|
|
20
|
+
__export(compare_media_exports, {
|
|
21
|
+
compareMedia: () => compareMedia
|
|
22
|
+
});
|
|
23
|
+
module.exports = __toCommonJS(compare_media_exports);
|
|
24
|
+
const compareMedia = (optionA, optionB) => {
|
|
25
|
+
if (optionA?.minWidth !== void 0 && optionB?.minWidth !== void 0) {
|
|
26
|
+
return optionA.minWidth - optionB.minWidth;
|
|
27
|
+
}
|
|
28
|
+
if (optionA?.maxWidth !== void 0 && optionB?.maxWidth !== void 0) {
|
|
29
|
+
return optionB.maxWidth - optionA.maxWidth;
|
|
30
|
+
}
|
|
31
|
+
return "minWidth" in optionA ? 1 : -1;
|
|
32
|
+
};
|
|
@@ -40,6 +40,7 @@ __export(css_engine_exports, {
|
|
|
40
40
|
});
|
|
41
41
|
module.exports = __toCommonJS(css_engine_exports);
|
|
42
42
|
var import_rules = require("./rules");
|
|
43
|
+
var import_compare_media = require("./compare-media");
|
|
43
44
|
var import_style_element = require("./style-element");
|
|
44
45
|
var import_style_sheet = require("./style-sheet");
|
|
45
46
|
var _element, _mediaRules, _plainRules, _fontFaceRules, _sheet, _isDirty, _cssText, _onChangeRule;
|
|
@@ -120,7 +121,10 @@ class CssEngine {
|
|
|
120
121
|
for (const plaintextRule of __privateGet(this, _plainRules).values()) {
|
|
121
122
|
css.push(plaintextRule.cssText);
|
|
122
123
|
}
|
|
123
|
-
|
|
124
|
+
const sortedMediaRules = Array.from(__privateGet(this, _mediaRules).values()).sort(
|
|
125
|
+
(ruleA, ruleB) => (0, import_compare_media.compareMedia)(ruleA.options, ruleB.options)
|
|
126
|
+
);
|
|
127
|
+
for (const mediaRule of sortedMediaRules) {
|
|
124
128
|
const { cssText } = mediaRule;
|
|
125
129
|
if (cssText !== "") {
|
|
126
130
|
css.push(cssText);
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __defProp = Object.defineProperty;
|
|
3
|
+
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
4
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
5
|
+
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
6
|
+
var __export = (target, all) => {
|
|
7
|
+
for (var name in all)
|
|
8
|
+
__defProp(target, name, { get: all[name], enumerable: true });
|
|
9
|
+
};
|
|
10
|
+
var __copyProps = (to, from, except, desc) => {
|
|
11
|
+
if (from && typeof from === "object" || typeof from === "function") {
|
|
12
|
+
for (let key of __getOwnPropNames(from))
|
|
13
|
+
if (!__hasOwnProp.call(to, key) && key !== except)
|
|
14
|
+
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
|
15
|
+
}
|
|
16
|
+
return to;
|
|
17
|
+
};
|
|
18
|
+
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
19
|
+
var equal_media_exports = {};
|
|
20
|
+
__export(equal_media_exports, {
|
|
21
|
+
equalMedia: () => equalMedia
|
|
22
|
+
});
|
|
23
|
+
module.exports = __toCommonJS(equal_media_exports);
|
|
24
|
+
const equalMedia = (left, right) => {
|
|
25
|
+
return left.minWidth === right.minWidth && left.maxWidth === right.maxWidth;
|
|
26
|
+
};
|
|
@@ -25,3 +25,6 @@ module.exports = __toCommonJS(core_exports);
|
|
|
25
25
|
var import_css_engine = require("./css-engine");
|
|
26
26
|
__reExport(core_exports, require("./create-css-engine"), module.exports);
|
|
27
27
|
__reExport(core_exports, require("./to-value"), module.exports);
|
|
28
|
+
__reExport(core_exports, require("./match-media"), module.exports);
|
|
29
|
+
__reExport(core_exports, require("./equal-media"), module.exports);
|
|
30
|
+
__reExport(core_exports, require("./compare-media"), module.exports);
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __defProp = Object.defineProperty;
|
|
3
|
+
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
4
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
5
|
+
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
6
|
+
var __export = (target, all) => {
|
|
7
|
+
for (var name in all)
|
|
8
|
+
__defProp(target, name, { get: all[name], enumerable: true });
|
|
9
|
+
};
|
|
10
|
+
var __copyProps = (to, from, except, desc) => {
|
|
11
|
+
if (from && typeof from === "object" || typeof from === "function") {
|
|
12
|
+
for (let key of __getOwnPropNames(from))
|
|
13
|
+
if (!__hasOwnProp.call(to, key) && key !== except)
|
|
14
|
+
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
|
15
|
+
}
|
|
16
|
+
return to;
|
|
17
|
+
};
|
|
18
|
+
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
19
|
+
var match_media_exports = {};
|
|
20
|
+
__export(match_media_exports, {
|
|
21
|
+
matchMedia: () => matchMedia
|
|
22
|
+
});
|
|
23
|
+
module.exports = __toCommonJS(match_media_exports);
|
|
24
|
+
const matchMedia = (options, width) => {
|
|
25
|
+
const minWidth = options.minWidth ?? Number.MIN_SAFE_INTEGER;
|
|
26
|
+
const maxWidth = options.maxWidth ?? Number.MAX_SAFE_INTEGER;
|
|
27
|
+
return width >= minWidth && width <= maxWidth;
|
|
28
|
+
};
|
|
@@ -116,14 +116,6 @@ class MediaRule {
|
|
|
116
116
|
this.options = options;
|
|
117
117
|
__privateSet(this, _mediaType, options.mediaType ?? "all");
|
|
118
118
|
}
|
|
119
|
-
// Sort media rules by minWidth.
|
|
120
|
-
// Needed to ensure that more specific media rules are inserted after less specific ones.
|
|
121
|
-
// So that they get a higher specificity.
|
|
122
|
-
static sort(mediaRules) {
|
|
123
|
-
return Array.from(mediaRules).sort((ruleA, ruleB) => {
|
|
124
|
-
return (ruleA.options.minWidth ?? -Number.MAX_SAFE_INTEGER) - (ruleB.options.minWidth ?? -Number.MAX_SAFE_INTEGER);
|
|
125
|
-
});
|
|
126
|
-
}
|
|
127
119
|
insertRule(rule) {
|
|
128
120
|
this.rules.push(rule);
|
|
129
121
|
return rule;
|
|
@@ -139,15 +131,12 @@ class MediaRule {
|
|
|
139
131
|
let conditionText = "";
|
|
140
132
|
const { minWidth, maxWidth } = this.options;
|
|
141
133
|
if (minWidth !== void 0) {
|
|
142
|
-
conditionText = `min-width: ${minWidth}px`;
|
|
134
|
+
conditionText = ` and (min-width: ${minWidth}px)`;
|
|
143
135
|
}
|
|
144
136
|
if (maxWidth !== void 0) {
|
|
145
|
-
conditionText
|
|
146
|
-
}
|
|
147
|
-
if (conditionText) {
|
|
148
|
-
conditionText = `and (${conditionText}) `;
|
|
137
|
+
conditionText += ` and (max-width: ${maxWidth}px)`;
|
|
149
138
|
}
|
|
150
|
-
return `@media ${__privateGet(this, _mediaType)}
|
|
139
|
+
return `@media ${__privateGet(this, _mediaType)}${conditionText} {
|
|
151
140
|
${rules.join(
|
|
152
141
|
"\n"
|
|
153
142
|
)}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"type":"commonjs"}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
const compareMedia = (optionA, optionB) => {
|
|
2
|
+
if (optionA?.minWidth !== void 0 && optionB?.minWidth !== void 0) {
|
|
3
|
+
return optionA.minWidth - optionB.minWidth;
|
|
4
|
+
}
|
|
5
|
+
if (optionA?.maxWidth !== void 0 && optionB?.maxWidth !== void 0) {
|
|
6
|
+
return optionB.maxWidth - optionA.maxWidth;
|
|
7
|
+
}
|
|
8
|
+
return "minWidth" in optionA ? 1 : -1;
|
|
9
|
+
};
|
|
10
|
+
export {
|
|
11
|
+
compareMedia
|
|
12
|
+
};
|
package/lib/core/css-engine.js
CHANGED
|
@@ -23,6 +23,7 @@ import {
|
|
|
23
23
|
PlaintextRule,
|
|
24
24
|
StyleRule
|
|
25
25
|
} from "./rules";
|
|
26
|
+
import { compareMedia } from "./compare-media";
|
|
26
27
|
import { StyleElement } from "./style-element";
|
|
27
28
|
import { StyleSheet } from "./style-sheet";
|
|
28
29
|
const defaultMediaRuleId = "__default-media-rule__";
|
|
@@ -102,7 +103,10 @@ class CssEngine {
|
|
|
102
103
|
for (const plaintextRule of __privateGet(this, _plainRules).values()) {
|
|
103
104
|
css.push(plaintextRule.cssText);
|
|
104
105
|
}
|
|
105
|
-
|
|
106
|
+
const sortedMediaRules = Array.from(__privateGet(this, _mediaRules).values()).sort(
|
|
107
|
+
(ruleA, ruleB) => compareMedia(ruleA.options, ruleB.options)
|
|
108
|
+
);
|
|
109
|
+
for (const mediaRule of sortedMediaRules) {
|
|
106
110
|
const { cssText } = mediaRule;
|
|
107
111
|
if (cssText !== "") {
|
|
108
112
|
css.push(cssText);
|
package/lib/core/index.js
CHANGED
package/lib/core/rules.js
CHANGED
|
@@ -90,14 +90,6 @@ class MediaRule {
|
|
|
90
90
|
this.options = options;
|
|
91
91
|
__privateSet(this, _mediaType, options.mediaType ?? "all");
|
|
92
92
|
}
|
|
93
|
-
// Sort media rules by minWidth.
|
|
94
|
-
// Needed to ensure that more specific media rules are inserted after less specific ones.
|
|
95
|
-
// So that they get a higher specificity.
|
|
96
|
-
static sort(mediaRules) {
|
|
97
|
-
return Array.from(mediaRules).sort((ruleA, ruleB) => {
|
|
98
|
-
return (ruleA.options.minWidth ?? -Number.MAX_SAFE_INTEGER) - (ruleB.options.minWidth ?? -Number.MAX_SAFE_INTEGER);
|
|
99
|
-
});
|
|
100
|
-
}
|
|
101
93
|
insertRule(rule) {
|
|
102
94
|
this.rules.push(rule);
|
|
103
95
|
return rule;
|
|
@@ -113,15 +105,12 @@ class MediaRule {
|
|
|
113
105
|
let conditionText = "";
|
|
114
106
|
const { minWidth, maxWidth } = this.options;
|
|
115
107
|
if (minWidth !== void 0) {
|
|
116
|
-
conditionText = `min-width: ${minWidth}px`;
|
|
108
|
+
conditionText = ` and (min-width: ${minWidth}px)`;
|
|
117
109
|
}
|
|
118
110
|
if (maxWidth !== void 0) {
|
|
119
|
-
conditionText
|
|
120
|
-
}
|
|
121
|
-
if (conditionText) {
|
|
122
|
-
conditionText = `and (${conditionText}) `;
|
|
111
|
+
conditionText += ` and (max-width: ${maxWidth}px)`;
|
|
123
112
|
}
|
|
124
|
-
return `@media ${__privateGet(this, _mediaType)}
|
|
113
|
+
return `@media ${__privateGet(this, _mediaType)}${conditionText} {
|
|
125
114
|
${rules.join(
|
|
126
115
|
"\n"
|
|
127
116
|
)}
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
import type { MediaRuleOptions } from "./rules";
|
|
2
|
+
/**
|
|
3
|
+
* Sort by minWidth descending or maxWidth ascending
|
|
4
|
+
* We want media querries with bigger minWidth to override the smaller once, but the smaller maxWidth to override the bigger once.
|
|
5
|
+
*/
|
|
6
|
+
export declare const compareMedia: (optionA: MediaRuleOptions, optionB: MediaRuleOptions) => number;
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import type { CssRule } from "@webstudio-is/css-data";
|
|
2
|
+
import { MediaRule, PlaintextRule, StyleRule, type FontFaceOptions, type MediaRuleOptions } from "./rules";
|
|
3
|
+
export type CssEngineOptions = {
|
|
4
|
+
name?: string;
|
|
5
|
+
};
|
|
6
|
+
export declare class CssEngine {
|
|
7
|
+
#private;
|
|
8
|
+
constructor({ name }: CssEngineOptions);
|
|
9
|
+
addMediaRule(id: string, options?: MediaRuleOptions): MediaRule;
|
|
10
|
+
addStyleRule(selectorText: string, rule: CssRule): StyleRule;
|
|
11
|
+
addPlaintextRule(cssText: string): PlaintextRule | Map<string, PlaintextRule>;
|
|
12
|
+
addFontFaceRule(options: FontFaceOptions): number;
|
|
13
|
+
clear(): void;
|
|
14
|
+
render(): void;
|
|
15
|
+
unmount(): void;
|
|
16
|
+
setAttribute(name: string, value: string): void;
|
|
17
|
+
getAttribute(name: string): string | null | undefined;
|
|
18
|
+
get cssText(): string;
|
|
19
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
export { CssEngine } from "./css-engine";
|
|
2
|
+
export type { AnyRule, StyleRule, MediaRule, PlaintextRule, FontFaceRule, } from "./rules";
|
|
3
|
+
export * from "./create-css-engine";
|
|
4
|
+
export * from "./to-value";
|
|
5
|
+
export * from "./match-media";
|
|
6
|
+
export * from "./equal-media";
|
|
7
|
+
export * from "./compare-media";
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
import type { Style, StyleProperty, StyleValue } from "@webstudio-is/css-data";
|
|
2
|
+
declare class StylePropertyMap {
|
|
3
|
+
#private;
|
|
4
|
+
onChange?: () => void;
|
|
5
|
+
set(property: StyleProperty, value?: StyleValue): void;
|
|
6
|
+
has(property: StyleProperty): boolean;
|
|
7
|
+
keys(): IterableIterator<"color" | "left" | "right" | "top" | "bottom" | "contain" | "clip" | "content" | "filter" | "float" | "fontFamily" | "width" | "height" | `--${string}` | "accentColor" | "alignContent" | "alignItems" | "alignSelf" | "alignTracks" | "animationComposition" | "animationDelay" | "animationDirection" | "animationDuration" | "animationFillMode" | "animationIterationCount" | "animationName" | "animationPlayState" | "animationTimingFunction" | "animationTimeline" | "appearance" | "aspectRatio" | "backdropFilter" | "backfaceVisibility" | "backgroundAttachment" | "backgroundBlendMode" | "backgroundClip" | "backgroundColor" | "backgroundImage" | "backgroundOrigin" | "backgroundPosition" | "backgroundPositionX" | "backgroundPositionY" | "backgroundRepeat" | "backgroundSize" | "blockOverflow" | "blockSize" | "borderBlockColor" | "borderBlockStyle" | "borderBlockWidth" | "borderBlockEndColor" | "borderBlockEndStyle" | "borderBlockEndWidth" | "borderBlockStartColor" | "borderBlockStartStyle" | "borderBlockStartWidth" | "borderBottomColor" | "borderBottomLeftRadius" | "borderBottomRightRadius" | "borderBottomStyle" | "borderBottomWidth" | "borderCollapse" | "borderEndEndRadius" | "borderEndStartRadius" | "borderImageOutset" | "borderImageRepeat" | "borderImageSlice" | "borderImageSource" | "borderImageWidth" | "borderInlineColor" | "borderInlineStyle" | "borderInlineWidth" | "borderInlineEndColor" | "borderInlineEndStyle" | "borderInlineEndWidth" | "borderInlineStartColor" | "borderInlineStartStyle" | "borderInlineStartWidth" | "borderLeftColor" | "borderLeftStyle" | "borderLeftWidth" | "borderRightColor" | "borderRightStyle" | "borderRightWidth" | "borderSpacing" | "borderStartEndRadius" | "borderStartStartRadius" | "borderTopColor" | "borderTopLeftRadius" | "borderTopRightRadius" | "borderTopStyle" | "borderTopWidth" | "boxDecorationBreak" | "boxShadow" | "boxSizing" | "breakAfter" | "breakBefore" | "breakInside" | "captionSide" | "caretColor" | "caretShape" | "clear" | "clipPath" | "printColorAdjust" | "colorScheme" | "columnCount" | "columnFill" | "columnGap" | "columnRuleColor" | "columnRuleStyle" | "columnRuleWidth" | "columnSpan" | "columnWidth" | "containIntrinsicBlockSize" | "containIntrinsicHeight" | "containIntrinsicInlineSize" | "containIntrinsicWidth" | "contentVisibility" | "counterIncrement" | "counterReset" | "counterSet" | "cursor" | "direction" | "display" | "emptyCells" | "flexBasis" | "flexDirection" | "flexGrow" | "flexShrink" | "flexWrap" | "fontFeatureSettings" | "fontKerning" | "fontLanguageOverride" | "fontOpticalSizing" | "fontVariationSettings" | "fontSize" | "fontSizeAdjust" | "fontStretch" | "fontStyle" | "fontSynthesis" | "fontVariant" | "fontVariantAlternates" | "fontVariantCaps" | "fontVariantEastAsian" | "fontVariantLigatures" | "fontVariantNumeric" | "fontVariantPosition" | "fontWeight" | "forcedColorAdjust" | "gridAutoColumns" | "gridAutoFlow" | "gridAutoRows" | "gridColumnEnd" | "gridColumnStart" | "gridRowEnd" | "gridRowStart" | "gridTemplateAreas" | "gridTemplateColumns" | "gridTemplateRows" | "hangingPunctuation" | "hyphenateCharacter" | "hyphens" | "imageOrientation" | "imageRendering" | "imageResolution" | "initialLetter" | "initialLetterAlign" | "inlineSize" | "inputSecurity" | "insetBlockEnd" | "insetBlockStart" | "insetInlineEnd" | "insetInlineStart" | "isolation" | "justifyContent" | "justifyItems" | "justifySelf" | "justifyTracks" | "letterSpacing" | "lineBreak" | "lineClamp" | "lineHeight" | "lineHeightStep" | "listStyleImage" | "listStylePosition" | "listStyleType" | "marginBlockEnd" | "marginBlockStart" | "marginBottom" | "marginInlineEnd" | "marginInlineStart" | "marginLeft" | "marginRight" | "marginTop" | "marginTrim" | "maskBorderMode" | "maskBorderOutset" | "maskBorderRepeat" | "maskBorderSlice" | "maskBorderSource" | "maskBorderWidth" | "maskClip" | "maskComposite" | "maskImage" | "maskMode" | "maskOrigin" | "maskPosition" | "maskRepeat" | "maskSize" | "maskType" | "masonryAutoFlow" | "mathDepth" | "mathShift" | "mathStyle" | "maxBlockSize" | "maxHeight" | "maxInlineSize" | "maxLines" | "maxWidth" | "minBlockSize" | "minHeight" | "minInlineSize" | "minWidth" | "mixBlendMode" | "objectFit" | "objectPosition" | "offsetAnchor" | "offsetDistance" | "offsetPath" | "offsetPosition" | "offsetRotate" | "opacity" | "order" | "orphans" | "outlineColor" | "outlineOffset" | "outlineStyle" | "outlineWidth" | "overflow" | "overflowAnchor" | "overflowBlock" | "overflowClipMargin" | "overflowInline" | "overflowWrap" | "overflowX" | "overflowY" | "overscrollBehavior" | "overscrollBehaviorBlock" | "overscrollBehaviorInline" | "overscrollBehaviorX" | "overscrollBehaviorY" | "paddingBlockEnd" | "paddingBlockStart" | "paddingBottom" | "paddingInlineEnd" | "paddingInlineStart" | "paddingLeft" | "paddingRight" | "paddingTop" | "pageBreakAfter" | "pageBreakBefore" | "pageBreakInside" | "paintOrder" | "perspective" | "perspectiveOrigin" | "pointerEvents" | "position" | "quotes" | "resize" | "rotate" | "rowGap" | "rubyAlign" | "rubyMerge" | "rubyPosition" | "scale" | "scrollbarColor" | "scrollbarGutter" | "scrollbarWidth" | "scrollBehavior" | "scrollMarginBlockStart" | "scrollMarginBlockEnd" | "scrollMarginBottom" | "scrollMarginInlineStart" | "scrollMarginInlineEnd" | "scrollMarginLeft" | "scrollMarginRight" | "scrollMarginTop" | "scrollPaddingBlockStart" | "scrollPaddingBlockEnd" | "scrollPaddingBottom" | "scrollPaddingInlineStart" | "scrollPaddingInlineEnd" | "scrollPaddingLeft" | "scrollPaddingRight" | "scrollPaddingTop" | "scrollSnapAlign" | "scrollSnapStop" | "scrollSnapType" | "scrollTimelineAxis" | "scrollTimelineName" | "shapeImageThreshold" | "shapeMargin" | "shapeOutside" | "tabSize" | "tableLayout" | "textAlign" | "textAlignLast" | "textCombineUpright" | "textDecorationColor" | "textDecorationLine" | "textDecorationSkip" | "textDecorationSkipInk" | "textDecorationStyle" | "textDecorationThickness" | "textEmphasisColor" | "textEmphasisPosition" | "textEmphasisStyle" | "textIndent" | "textJustify" | "textOrientation" | "textOverflow" | "textRendering" | "textShadow" | "textSizeAdjust" | "textTransform" | "textUnderlineOffset" | "textUnderlinePosition" | "touchAction" | "transform" | "transformBox" | "transformOrigin" | "transformStyle" | "transitionDelay" | "transitionDuration" | "transitionProperty" | "transitionTimingFunction" | "translate" | "unicodeBidi" | "userSelect" | "verticalAlign" | "visibility" | "whiteSpace" | "widows" | "willChange" | "wordBreak" | "wordSpacing" | "wordWrap" | "writingMode" | "zIndex">;
|
|
8
|
+
delete(property: StyleProperty): void;
|
|
9
|
+
clear(): void;
|
|
10
|
+
toString(): string;
|
|
11
|
+
}
|
|
12
|
+
export declare class StyleRule {
|
|
13
|
+
#private;
|
|
14
|
+
styleMap: StylePropertyMap;
|
|
15
|
+
selectorText: string;
|
|
16
|
+
onChange?: () => void;
|
|
17
|
+
constructor(selectorText: string, style: Style);
|
|
18
|
+
get cssText(): string;
|
|
19
|
+
}
|
|
20
|
+
export type MediaRuleOptions = {
|
|
21
|
+
minWidth?: number;
|
|
22
|
+
maxWidth?: number;
|
|
23
|
+
mediaType?: "all" | "screen" | "print";
|
|
24
|
+
};
|
|
25
|
+
export declare class MediaRule {
|
|
26
|
+
#private;
|
|
27
|
+
options: MediaRuleOptions;
|
|
28
|
+
rules: Array<StyleRule | PlaintextRule>;
|
|
29
|
+
constructor(options?: MediaRuleOptions);
|
|
30
|
+
insertRule(rule: StyleRule | PlaintextRule): StyleRule | PlaintextRule;
|
|
31
|
+
get cssText(): string;
|
|
32
|
+
}
|
|
33
|
+
export declare class PlaintextRule {
|
|
34
|
+
cssText: string;
|
|
35
|
+
styleMap: StylePropertyMap;
|
|
36
|
+
constructor(cssText: string);
|
|
37
|
+
}
|
|
38
|
+
export type FontFaceOptions = {
|
|
39
|
+
fontFamily: string;
|
|
40
|
+
fontStyle?: "normal" | "italic" | "oblique";
|
|
41
|
+
fontWeight?: number | string;
|
|
42
|
+
fontDisplay: "swap" | "auto" | "block" | "fallback" | "optional";
|
|
43
|
+
src: string;
|
|
44
|
+
};
|
|
45
|
+
export declare class FontFaceRule {
|
|
46
|
+
options: FontFaceOptions;
|
|
47
|
+
constructor(options: FontFaceOptions);
|
|
48
|
+
get cssText(): string;
|
|
49
|
+
}
|
|
50
|
+
export type AnyRule = StyleRule | MediaRule | PlaintextRule | FontFaceRule;
|
|
51
|
+
export {};
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
export declare class StyleElement {
|
|
2
|
+
#private;
|
|
3
|
+
constructor(name?: string);
|
|
4
|
+
get isMounted(): boolean;
|
|
5
|
+
mount(): void;
|
|
6
|
+
unmount(): void;
|
|
7
|
+
render(cssText: string): void;
|
|
8
|
+
setAttribute(name: string, value: string): void;
|
|
9
|
+
getAttribute(name: string): string | null | undefined;
|
|
10
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export * from "./core";
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@webstudio-is/css-engine",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.52.0",
|
|
4
4
|
"description": "CSS Renderer for Webstudio",
|
|
5
5
|
"author": "Webstudio <github@webstudio.is>",
|
|
6
6
|
"homepage": "https://webstudio.is",
|
|
@@ -9,7 +9,7 @@
|
|
|
9
9
|
"hyphenate-style-name": "^1.0.4",
|
|
10
10
|
"react": "^17.0.2",
|
|
11
11
|
"react-dom": "^17.0.2",
|
|
12
|
-
"@webstudio-is/fonts": "^0.
|
|
12
|
+
"@webstudio-is/fonts": "^0.52.0"
|
|
13
13
|
},
|
|
14
14
|
"devDependencies": {
|
|
15
15
|
"@jest/globals": "^29.3.1",
|
|
@@ -18,8 +18,8 @@
|
|
|
18
18
|
"@types/react": "^17.0.24",
|
|
19
19
|
"@types/react-dom": "^17.0.9",
|
|
20
20
|
"jest": "^29.3.1",
|
|
21
|
-
"typescript": "
|
|
22
|
-
"@webstudio-is/css-data": "^0.
|
|
21
|
+
"typescript": "5.0.3",
|
|
22
|
+
"@webstudio-is/css-data": "^0.52.0",
|
|
23
23
|
"@webstudio-is/jest-config": "^1.0.2",
|
|
24
24
|
"@webstudio-is/scripts": "^0.0.0",
|
|
25
25
|
"@webstudio-is/storybook-config": "^0.0.0",
|
|
@@ -29,7 +29,7 @@
|
|
|
29
29
|
"source": "./src/index.ts",
|
|
30
30
|
"import": "./lib/index.js"
|
|
31
31
|
},
|
|
32
|
-
"types": "
|
|
32
|
+
"types": "lib/types/index.d.ts",
|
|
33
33
|
"files": [
|
|
34
34
|
"lib/*",
|
|
35
35
|
"src/*",
|
|
@@ -43,6 +43,7 @@
|
|
|
43
43
|
"checks": "pnpm typecheck && pnpm lint && pnpm test",
|
|
44
44
|
"dev": "build-package --watch",
|
|
45
45
|
"build": "build-package",
|
|
46
|
+
"dts": "tsc --emitDeclarationOnly --declaration --declarationDir lib/types",
|
|
46
47
|
"test": "NODE_OPTIONS=--experimental-vm-modules jest",
|
|
47
48
|
"lint": "eslint ./src --ext .ts,.tsx --max-warnings 0",
|
|
48
49
|
"storybook:run": "start-storybook -p 6006",
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
import { describe, test, expect } from "@jest/globals";
|
|
2
|
+
import { compareMedia } from "./compare-media";
|
|
3
|
+
|
|
4
|
+
describe("Compare media", () => {
|
|
5
|
+
test("mobile first", () => {
|
|
6
|
+
const initial = [
|
|
7
|
+
{ minWidth: 0 },
|
|
8
|
+
{ minWidth: 768 },
|
|
9
|
+
{ minWidth: 1024 },
|
|
10
|
+
{ minWidth: 1280 },
|
|
11
|
+
];
|
|
12
|
+
expect(initial.sort(compareMedia)).toStrictEqual(initial);
|
|
13
|
+
});
|
|
14
|
+
|
|
15
|
+
test("random minWidth", () => {
|
|
16
|
+
const initial = [{ minWidth: 0 }, { minWidth: 3 }, { minWidth: 2 }];
|
|
17
|
+
const sorted = [{ minWidth: 0 }, { minWidth: 2 }, { minWidth: 3 }];
|
|
18
|
+
expect(initial.sort(compareMedia)).toStrictEqual(sorted);
|
|
19
|
+
});
|
|
20
|
+
|
|
21
|
+
test("webflow", () => {
|
|
22
|
+
const initial = [
|
|
23
|
+
{ maxWidth: 991 },
|
|
24
|
+
{ maxWidth: 767 },
|
|
25
|
+
{ maxWidth: 479 },
|
|
26
|
+
{ minWidth: 1280 },
|
|
27
|
+
{ minWidth: 1440 },
|
|
28
|
+
{ minWidth: 1920 },
|
|
29
|
+
];
|
|
30
|
+
const sorted = [
|
|
31
|
+
{ maxWidth: 991 },
|
|
32
|
+
{ maxWidth: 767 },
|
|
33
|
+
{ maxWidth: 479 },
|
|
34
|
+
{ minWidth: 1280 },
|
|
35
|
+
{ minWidth: 1440 },
|
|
36
|
+
{ minWidth: 1920 },
|
|
37
|
+
];
|
|
38
|
+
expect(initial.sort(compareMedia)).toStrictEqual(sorted);
|
|
39
|
+
});
|
|
40
|
+
});
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import type { MediaRuleOptions } from "./rules";
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Sort by minWidth descending or maxWidth ascending
|
|
5
|
+
* We want media querries with bigger minWidth to override the smaller once, but the smaller maxWidth to override the bigger once.
|
|
6
|
+
*/
|
|
7
|
+
export const compareMedia = (
|
|
8
|
+
optionA: MediaRuleOptions,
|
|
9
|
+
optionB: MediaRuleOptions
|
|
10
|
+
) => {
|
|
11
|
+
if (optionA?.minWidth !== undefined && optionB?.minWidth !== undefined) {
|
|
12
|
+
return optionA.minWidth - optionB.minWidth;
|
|
13
|
+
}
|
|
14
|
+
if (optionA?.maxWidth !== undefined && optionB?.maxWidth !== undefined) {
|
|
15
|
+
return optionB.maxWidth - optionA.maxWidth;
|
|
16
|
+
}
|
|
17
|
+
return "minWidth" in optionA ? 1 : -1;
|
|
18
|
+
};
|
|
@@ -29,6 +29,45 @@ describe("CssEngine", () => {
|
|
|
29
29
|
|
|
30
30
|
beforeEach(reset);
|
|
31
31
|
|
|
32
|
+
test("minWidth media rule", () => {
|
|
33
|
+
engine.addMediaRule("0", { minWidth: 0 });
|
|
34
|
+
engine.addStyleRule(".c1", {
|
|
35
|
+
style: { color: { type: "keyword", value: "red" } },
|
|
36
|
+
breakpoint: "0",
|
|
37
|
+
});
|
|
38
|
+
expect(engine.cssText).toMatchInlineSnapshot(`
|
|
39
|
+
"@media all and (min-width: 0px) {
|
|
40
|
+
.c1 { color: red }
|
|
41
|
+
}"
|
|
42
|
+
`);
|
|
43
|
+
});
|
|
44
|
+
|
|
45
|
+
test("maxWidth media rule", () => {
|
|
46
|
+
engine.addMediaRule("0", { maxWidth: 1000 });
|
|
47
|
+
engine.addStyleRule(".c1", {
|
|
48
|
+
style: { color: { type: "keyword", value: "red" } },
|
|
49
|
+
breakpoint: "0",
|
|
50
|
+
});
|
|
51
|
+
expect(engine.cssText).toMatchInlineSnapshot(`
|
|
52
|
+
"@media all and (max-width: 1000px) {
|
|
53
|
+
.c1 { color: red }
|
|
54
|
+
}"
|
|
55
|
+
`);
|
|
56
|
+
});
|
|
57
|
+
|
|
58
|
+
test("maxWidth and maxWith media rule", () => {
|
|
59
|
+
engine.addMediaRule("0", { maxWidth: 1000, minWidth: 360 });
|
|
60
|
+
engine.addStyleRule(".c1", {
|
|
61
|
+
style: { color: { type: "keyword", value: "red" } },
|
|
62
|
+
breakpoint: "0",
|
|
63
|
+
});
|
|
64
|
+
expect(engine.cssText).toMatchInlineSnapshot(`
|
|
65
|
+
"@media all and (min-width: 360px) and (max-width: 1000px) {
|
|
66
|
+
.c1 { color: red }
|
|
67
|
+
}"
|
|
68
|
+
`);
|
|
69
|
+
});
|
|
70
|
+
|
|
32
71
|
test("use default media rule when there is no matching one registered", () => {
|
|
33
72
|
engine.addStyleRule(".c", {
|
|
34
73
|
style: style0,
|
package/src/core/css-engine.ts
CHANGED
|
@@ -7,6 +7,7 @@ import {
|
|
|
7
7
|
type FontFaceOptions,
|
|
8
8
|
type MediaRuleOptions,
|
|
9
9
|
} from "./rules";
|
|
10
|
+
import { compareMedia } from "./compare-media";
|
|
10
11
|
import { StyleElement } from "./style-element";
|
|
11
12
|
import { StyleSheet } from "./style-sheet";
|
|
12
13
|
|
|
@@ -90,7 +91,11 @@ export class CssEngine {
|
|
|
90
91
|
for (const plaintextRule of this.#plainRules.values()) {
|
|
91
92
|
css.push(plaintextRule.cssText);
|
|
92
93
|
}
|
|
93
|
-
|
|
94
|
+
|
|
95
|
+
const sortedMediaRules = Array.from(this.#mediaRules.values()).sort(
|
|
96
|
+
(ruleA, ruleB) => compareMedia(ruleA.options, ruleB.options)
|
|
97
|
+
);
|
|
98
|
+
for (const mediaRule of sortedMediaRules) {
|
|
94
99
|
const { cssText } = mediaRule;
|
|
95
100
|
if (cssText !== "") {
|
|
96
101
|
css.push(cssText);
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import { describe, expect, test } from "@jest/globals";
|
|
2
|
+
import { equalMedia } from "./equal-media";
|
|
3
|
+
|
|
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
|
+
|
|
11
|
+
test("maxWidth", () => {
|
|
12
|
+
expect(equalMedia({ maxWidth: 100 }, { maxWidth: 101 })).toBe(false);
|
|
13
|
+
expect(equalMedia({ maxWidth: 100 }, { maxWidth: 100 })).toBe(true);
|
|
14
|
+
expect(equalMedia({ maxWidth: 100 }, { maxWidth: 10 })).toBe(false);
|
|
15
|
+
});
|
|
16
|
+
|
|
17
|
+
test("minWidth and maxWidth", () => {
|
|
18
|
+
expect(equalMedia({ maxWidth: 100, minWidth: 10 }, { maxWidth: 100 })).toBe(
|
|
19
|
+
false
|
|
20
|
+
);
|
|
21
|
+
expect(equalMedia({ maxWidth: 100, minWidth: 10 }, { minWidth: 10 })).toBe(
|
|
22
|
+
false
|
|
23
|
+
);
|
|
24
|
+
expect(
|
|
25
|
+
equalMedia(
|
|
26
|
+
{ maxWidth: 100, minWidth: 10 },
|
|
27
|
+
{ maxWidth: 100, minWidth: 10 }
|
|
28
|
+
)
|
|
29
|
+
).toBe(true);
|
|
30
|
+
});
|
|
31
|
+
});
|
package/src/core/index.ts
CHANGED
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import { describe, expect, test } from "@jest/globals";
|
|
2
|
+
import { matchMedia } from "./match-media";
|
|
3
|
+
|
|
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
|
+
|
|
11
|
+
test("maxWidth", () => {
|
|
12
|
+
expect(matchMedia({ maxWidth: 100 }, 101)).toBe(false);
|
|
13
|
+
expect(matchMedia({ maxWidth: 100 }, 100)).toBe(true);
|
|
14
|
+
expect(matchMedia({ maxWidth: 100 }, 10)).toBe(true);
|
|
15
|
+
});
|
|
16
|
+
|
|
17
|
+
test("minWidth and maxWidth", () => {
|
|
18
|
+
expect(matchMedia({ maxWidth: 100, minWidth: 10 }, 9)).toBe(false);
|
|
19
|
+
expect(matchMedia({ maxWidth: 100, minWidth: 10 }, 101)).toBe(false);
|
|
20
|
+
expect(matchMedia({ maxWidth: 100, minWidth: 10 }, 100)).toBe(true);
|
|
21
|
+
expect(matchMedia({ maxWidth: 100, minWidth: 10 }, 10)).toBe(true);
|
|
22
|
+
expect(matchMedia({ maxWidth: 100, minWidth: 10 }, 11)).toBe(true);
|
|
23
|
+
});
|
|
24
|
+
});
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
import type { MediaRuleOptions } from "./rules";
|
|
2
|
+
|
|
3
|
+
export const matchMedia = (options: MediaRuleOptions, width: number) => {
|
|
4
|
+
const minWidth = options.minWidth ?? Number.MIN_SAFE_INTEGER;
|
|
5
|
+
const maxWidth = options.maxWidth ?? Number.MAX_SAFE_INTEGER;
|
|
6
|
+
return width >= minWidth && width <= maxWidth;
|
|
7
|
+
};
|
package/src/core/rules.ts
CHANGED
|
@@ -73,17 +73,6 @@ export type MediaRuleOptions = {
|
|
|
73
73
|
};
|
|
74
74
|
|
|
75
75
|
export class MediaRule {
|
|
76
|
-
// Sort media rules by minWidth.
|
|
77
|
-
// Needed to ensure that more specific media rules are inserted after less specific ones.
|
|
78
|
-
// So that they get a higher specificity.
|
|
79
|
-
static sort(mediaRules: Iterable<MediaRule>) {
|
|
80
|
-
return Array.from(mediaRules).sort((ruleA, ruleB) => {
|
|
81
|
-
return (
|
|
82
|
-
(ruleA.options.minWidth ?? -Number.MAX_SAFE_INTEGER) -
|
|
83
|
-
(ruleB.options.minWidth ?? -Number.MAX_SAFE_INTEGER)
|
|
84
|
-
);
|
|
85
|
-
});
|
|
86
|
-
}
|
|
87
76
|
options: MediaRuleOptions;
|
|
88
77
|
rules: Array<StyleRule | PlaintextRule> = [];
|
|
89
78
|
#mediaType;
|
|
@@ -106,15 +95,12 @@ export class MediaRule {
|
|
|
106
95
|
let conditionText = "";
|
|
107
96
|
const { minWidth, maxWidth } = this.options;
|
|
108
97
|
if (minWidth !== undefined) {
|
|
109
|
-
conditionText = `min-width: ${minWidth}px`;
|
|
98
|
+
conditionText = ` and (min-width: ${minWidth}px)`;
|
|
110
99
|
}
|
|
111
100
|
if (maxWidth !== undefined) {
|
|
112
|
-
conditionText
|
|
113
|
-
}
|
|
114
|
-
if (conditionText) {
|
|
115
|
-
conditionText = `and (${conditionText}) `;
|
|
101
|
+
conditionText += ` and (max-width: ${maxWidth}px)`;
|
|
116
102
|
}
|
|
117
|
-
return `@media ${this.#mediaType}
|
|
103
|
+
return `@media ${this.#mediaType}${conditionText} {\n${rules.join(
|
|
118
104
|
"\n"
|
|
119
105
|
)}\n}`;
|
|
120
106
|
}
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|