sheetom 0.1.0-rc.1 → 0.1.0-rc.2
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/README.md +11 -3
- package/compatibility/baselines/0.1.0-rc.2.json +142 -0
- package/compatibility/schemas/compatibility-report.schema.json +45 -3
- package/dist/index.cjs +115 -5
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +6 -2
- package/dist/index.d.ts +6 -2
- package/dist/index.js +115 -5
- package/dist/index.js.map +1 -1
- package/docs/api.md +4 -1
- package/docs/releasing.md +44 -58
- package/package.json +6 -5
package/README.md
CHANGED
|
@@ -96,7 +96,10 @@ block remote resources, or make valid hostile CSS safe to render.
|
|
|
96
96
|
|
|
97
97
|
Invalid `setProperty` values and priorities are atomic no-ops, matching browser
|
|
98
98
|
behavior. Opt into mutation diagnostics with `{ diagnostics: true }` and drain
|
|
99
|
-
them using `takeDiagnostics()`.
|
|
99
|
+
them using `takeDiagnostics()`. `SheetOMDiagnostic.code` is a stable string
|
|
100
|
+
union. Diagnostics retain the complete rejected input until drained; when
|
|
101
|
+
processing untrusted or very large input, callers should leave diagnostics off
|
|
102
|
+
or drain them promptly to bound memory use.
|
|
100
103
|
|
|
101
104
|
## API scope
|
|
102
105
|
|
|
@@ -153,8 +156,13 @@ npm run pack:test
|
|
|
153
156
|
|
|
154
157
|
`npm test` runs unit, deterministic fuzz, and local Chromium projects. Use
|
|
155
158
|
`npm run test:browser:matrix` on a machine with all Playwright dependencies to
|
|
156
|
-
run Chromium, Firefox, and WebKit.
|
|
157
|
-
|
|
159
|
+
run Chromium, Firefox, and WebKit. The browser matrix also runs a deterministic,
|
|
160
|
+
grammar-oriented declaration differential; any minimized mismatch must become
|
|
161
|
+
an immutable Operation Fixture with an explicit Compatibility Resolution.
|
|
162
|
+
`npm run docs:build` generates the TypeDoc reference under `site/api`. The benchmark gates both a 10,000-rule stress case
|
|
163
|
+
and a publisher-shaped reference workload with one shared stylesheet, 20 page
|
|
164
|
+
stylesheets, nested layers/media, distributed mutations, and two idempotent
|
|
165
|
+
serialization passes.
|
|
158
166
|
|
|
159
167
|
Only the latest published `0.x` minor and its active prereleases receive fixes
|
|
160
168
|
before 1.0. During `0.x`, observable compatibility corrections and interface
|
|
@@ -0,0 +1,142 @@
|
|
|
1
|
+
{
|
|
2
|
+
"$schema": "../schemas/compatibility-report.schema.json",
|
|
3
|
+
"schemaVersion": 2,
|
|
4
|
+
"packageVersion": "0.1.0-rc.2",
|
|
5
|
+
"baseline": {
|
|
6
|
+
"wptCommit": "54f8f933629e7c010ae98a246729af01f8abcda5",
|
|
7
|
+
"syntaxEngineSet": {
|
|
8
|
+
"lightningcss": "1.33.0",
|
|
9
|
+
"cssTree": "3.2.1",
|
|
10
|
+
"cssstyle": "6.2.0",
|
|
11
|
+
"cssTokenizer": "4.0.0"
|
|
12
|
+
},
|
|
13
|
+
"runtimes": {
|
|
14
|
+
"node": "v24.18.0",
|
|
15
|
+
"bun": "1.3.1",
|
|
16
|
+
"deno": "2.9.5"
|
|
17
|
+
},
|
|
18
|
+
"browsers": {
|
|
19
|
+
"chromium": "151.0.7922.34 (revision 1234)",
|
|
20
|
+
"firefox": "153.0 (revision 1538)",
|
|
21
|
+
"webkit": "26.5 (revision 2336)"
|
|
22
|
+
}
|
|
23
|
+
},
|
|
24
|
+
"summary": {
|
|
25
|
+
"passed": 6,
|
|
26
|
+
"divergences": 7,
|
|
27
|
+
"excluded": 0,
|
|
28
|
+
"unexplained": 0
|
|
29
|
+
},
|
|
30
|
+
"evidence": {
|
|
31
|
+
"operationFixtures": {
|
|
32
|
+
"passed": 13,
|
|
33
|
+
"total": 13,
|
|
34
|
+
"sha256": "7909f90571b8d6595d18b006bb4f2c5337cca9d413084558e913e760be45a3aa",
|
|
35
|
+
"adapters": [
|
|
36
|
+
{
|
|
37
|
+
"adapter": "sheetom",
|
|
38
|
+
"passed": 13,
|
|
39
|
+
"total": 13
|
|
40
|
+
},
|
|
41
|
+
{
|
|
42
|
+
"adapter": "chromium",
|
|
43
|
+
"passed": 13,
|
|
44
|
+
"total": 13
|
|
45
|
+
},
|
|
46
|
+
{
|
|
47
|
+
"adapter": "firefox",
|
|
48
|
+
"passed": 13,
|
|
49
|
+
"total": 13
|
|
50
|
+
},
|
|
51
|
+
{
|
|
52
|
+
"adapter": "webkit",
|
|
53
|
+
"passed": 13,
|
|
54
|
+
"total": 13
|
|
55
|
+
}
|
|
56
|
+
]
|
|
57
|
+
},
|
|
58
|
+
"nativeWpt": [
|
|
59
|
+
{
|
|
60
|
+
"engine": "chrome",
|
|
61
|
+
"version": "150.0.7871.128",
|
|
62
|
+
"sha256": "3f66a5f324bb9023cdd4b168e780640700449f22f28ca378eca91504b7d68e3f",
|
|
63
|
+
"passed": 4,
|
|
64
|
+
"total": 4
|
|
65
|
+
},
|
|
66
|
+
{
|
|
67
|
+
"engine": "firefox",
|
|
68
|
+
"version": "153.0.3",
|
|
69
|
+
"sha256": "e5f88e3431f602dc29fd8dbd79667bef33930ee7f87ee6d2069f17126421a230",
|
|
70
|
+
"passed": 4,
|
|
71
|
+
"total": 4
|
|
72
|
+
}
|
|
73
|
+
]
|
|
74
|
+
},
|
|
75
|
+
"resolutions": [
|
|
76
|
+
{
|
|
77
|
+
"fixtureId": "declarations.basic-set-property",
|
|
78
|
+
"decision": "shared",
|
|
79
|
+
"rationale": "The observable declaration state is shared by the pinned browser engines."
|
|
80
|
+
},
|
|
81
|
+
{
|
|
82
|
+
"fixtureId": "declarations.malformed-padding",
|
|
83
|
+
"decision": "chromium-fallback",
|
|
84
|
+
"rationale": "CSS Syntax accepts the EOF-recovered value; Chromium preserves the unclosed observable spelling while Firefox materializes closing parentheses."
|
|
85
|
+
},
|
|
86
|
+
{
|
|
87
|
+
"fixtureId": "declarations.modern-color-canonicalization",
|
|
88
|
+
"decision": "shared",
|
|
89
|
+
"rationale": "The pinned browser engines expose modern rgb syntax through the shared legacy rgba serialization in CSSStyleDeclaration."
|
|
90
|
+
},
|
|
91
|
+
{
|
|
92
|
+
"fixtureId": "declarations.typed-eof-recovery",
|
|
93
|
+
"decision": "chromium-fallback",
|
|
94
|
+
"rationale": "Typed property values expose the Chromium/WebKit canonical serialization after CSS Syntax EOF recovery; Firefox retains quotes around the recovered font-family string."
|
|
95
|
+
},
|
|
96
|
+
{
|
|
97
|
+
"fixtureId": "declarations.quoted-font-family",
|
|
98
|
+
"decision": "chromium-fallback",
|
|
99
|
+
"rationale": "Chromium and WebKit remove unnecessary quotes around a simple font-family while Firefox retains them; SheetOM follows the measured Chromium fallback."
|
|
100
|
+
},
|
|
101
|
+
{
|
|
102
|
+
"fixtureId": "declarations.custom-property-eof-recovery",
|
|
103
|
+
"decision": "chromium-fallback",
|
|
104
|
+
"rationale": "Chromium retains unclosed custom-property token structures, removes EOF comments, and replaces an escaped EOF with U+FFFD."
|
|
105
|
+
},
|
|
106
|
+
{
|
|
107
|
+
"fixtureId": "declarations.custom-property-priority-replacement",
|
|
108
|
+
"decision": "chromium-fallback",
|
|
109
|
+
"rationale": "Chromium and Firefox replace the existing custom-property priority when setProperty is called without a priority, while WebKit retains !important; SheetOM follows the measured Chromium fallback."
|
|
110
|
+
},
|
|
111
|
+
{
|
|
112
|
+
"fixtureId": "declarations.pending-substitution-eof-recovery",
|
|
113
|
+
"decision": "chromium-fallback",
|
|
114
|
+
"rationale": "Chromium retains the unclosed pending-substitution spelling while applying lexical EOF recovery such as comment removal."
|
|
115
|
+
},
|
|
116
|
+
{
|
|
117
|
+
"fixtureId": "declarations.setproperty-null-undefined",
|
|
118
|
+
"decision": "shared",
|
|
119
|
+
"rationale": "The four adapted WPT subtests define the shared null and undefined conversion behavior."
|
|
120
|
+
},
|
|
121
|
+
{
|
|
122
|
+
"fixtureId": "declarations.required-arguments",
|
|
123
|
+
"decision": "shared",
|
|
124
|
+
"rationale": "Required WebIDL arguments throw TypeError before string conversion in the pinned browser engines."
|
|
125
|
+
},
|
|
126
|
+
{
|
|
127
|
+
"fixtureId": "declarations.set-property-multiple-declarations",
|
|
128
|
+
"decision": "shared",
|
|
129
|
+
"rationale": "A setProperty value is parsed independently and cannot escape into another declaration; invalid input leaves the previous declaration unchanged."
|
|
130
|
+
},
|
|
131
|
+
{
|
|
132
|
+
"fixtureId": "declarations.escaped-custom-property-name",
|
|
133
|
+
"decision": "shared",
|
|
134
|
+
"rationale": "Custom-property method arguments retain their logical names and cssText escapes those names only when serializing CSS syntax."
|
|
135
|
+
},
|
|
136
|
+
{
|
|
137
|
+
"fixtureId": "integration.weweb-authoring-tree",
|
|
138
|
+
"decision": "chromium-fallback",
|
|
139
|
+
"rationale": "The standard rule-tree operations and detachment identity are shared; the recovered padding spelling follows the existing Chromium fallback resolution."
|
|
140
|
+
}
|
|
141
|
+
]
|
|
142
|
+
}
|
|
@@ -7,16 +7,27 @@
|
|
|
7
7
|
"required": ["schemaVersion", "packageVersion", "baseline", "summary", "evidence", "resolutions"],
|
|
8
8
|
"properties": {
|
|
9
9
|
"$schema": { "type": "string" },
|
|
10
|
-
"schemaVersion": { "
|
|
10
|
+
"schemaVersion": { "enum": [1, 2] },
|
|
11
11
|
"packageVersion": { "type": "string" },
|
|
12
12
|
"baseline": {
|
|
13
13
|
"type": "object",
|
|
14
14
|
"additionalProperties": false,
|
|
15
|
-
"required": ["wptCommit", "
|
|
15
|
+
"required": ["wptCommit", "runtimes", "browsers"],
|
|
16
16
|
"properties": {
|
|
17
17
|
"wptCommit": { "type": "string", "pattern": "^[0-9a-f]{40}$" },
|
|
18
18
|
"lightningcss": { "type": "string" },
|
|
19
19
|
"cssTree": { "type": "string" },
|
|
20
|
+
"syntaxEngineSet": {
|
|
21
|
+
"type": "object",
|
|
22
|
+
"additionalProperties": false,
|
|
23
|
+
"required": ["lightningcss", "cssTree", "cssstyle", "cssTokenizer"],
|
|
24
|
+
"properties": {
|
|
25
|
+
"lightningcss": { "type": "string" },
|
|
26
|
+
"cssTree": { "type": "string" },
|
|
27
|
+
"cssstyle": { "type": "string" },
|
|
28
|
+
"cssTokenizer": { "type": "string" }
|
|
29
|
+
}
|
|
30
|
+
},
|
|
20
31
|
"runtimes": { "type": "object" },
|
|
21
32
|
"browsers": { "type": "object" }
|
|
22
33
|
}
|
|
@@ -80,5 +91,36 @@
|
|
|
80
91
|
}
|
|
81
92
|
},
|
|
82
93
|
"resolutions": { "type": "array" }
|
|
83
|
-
}
|
|
94
|
+
},
|
|
95
|
+
"allOf": [
|
|
96
|
+
{
|
|
97
|
+
"if": { "properties": { "schemaVersion": { "const": 1 } } },
|
|
98
|
+
"then": {
|
|
99
|
+
"properties": {
|
|
100
|
+
"baseline": {
|
|
101
|
+
"type": "object",
|
|
102
|
+
"required": ["lightningcss", "cssTree"],
|
|
103
|
+
"properties": {
|
|
104
|
+
"lightningcss": { "type": "string" },
|
|
105
|
+
"cssTree": { "type": "string" }
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
},
|
|
111
|
+
{
|
|
112
|
+
"if": { "properties": { "schemaVersion": { "const": 2 } } },
|
|
113
|
+
"then": {
|
|
114
|
+
"properties": {
|
|
115
|
+
"baseline": {
|
|
116
|
+
"type": "object",
|
|
117
|
+
"required": ["syntaxEngineSet"],
|
|
118
|
+
"properties": {
|
|
119
|
+
"syntaxEngineSet": { "type": "object" }
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
]
|
|
84
126
|
}
|
package/dist/index.cjs
CHANGED
|
@@ -1975,6 +1975,87 @@ function expandStaticShorthand(name, parsed, important) {
|
|
|
1975
1975
|
return records;
|
|
1976
1976
|
}
|
|
1977
1977
|
//#endregion
|
|
1978
|
+
//#region src/internal/observable-value-codec.ts
|
|
1979
|
+
const closingToken = /* @__PURE__ */ new Map([
|
|
1980
|
+
[_csstools_css_tokenizer.TokenType.Function, {
|
|
1981
|
+
token: _csstools_css_tokenizer.TokenType.CloseParen,
|
|
1982
|
+
text: ")"
|
|
1983
|
+
}],
|
|
1984
|
+
[_csstools_css_tokenizer.TokenType.OpenParen, {
|
|
1985
|
+
token: _csstools_css_tokenizer.TokenType.CloseParen,
|
|
1986
|
+
text: ")"
|
|
1987
|
+
}],
|
|
1988
|
+
[_csstools_css_tokenizer.TokenType.OpenSquare, {
|
|
1989
|
+
token: _csstools_css_tokenizer.TokenType.CloseSquare,
|
|
1990
|
+
text: "]"
|
|
1991
|
+
}],
|
|
1992
|
+
[_csstools_css_tokenizer.TokenType.OpenCurly, {
|
|
1993
|
+
token: _csstools_css_tokenizer.TokenType.CloseCurly,
|
|
1994
|
+
text: "}"
|
|
1995
|
+
}]
|
|
1996
|
+
]);
|
|
1997
|
+
function escapedString(value, quote) {
|
|
1998
|
+
return value.replace(/\\/g, "\\\\").replace(new RegExp(quote, "g"), `\\${quote}`);
|
|
1999
|
+
}
|
|
2000
|
+
function retainedToken(token) {
|
|
2001
|
+
if (token[0] === _csstools_css_tokenizer.TokenType.Comment || token[0] === _csstools_css_tokenizer.TokenType.EOF) return "";
|
|
2002
|
+
const details = token[4];
|
|
2003
|
+
if (token[0] === _csstools_css_tokenizer.TokenType.Ident && token[1].endsWith("\\") && details && "value" in details && `${details.value}`.endsWith("�")) return `${token[1].slice(0, -1)}�`;
|
|
2004
|
+
if (token[0] === _csstools_css_tokenizer.TokenType.URL && token[1].endsWith("\\") && details && "value" in details) return `url(${details.value})`;
|
|
2005
|
+
if (token[0] === _csstools_css_tokenizer.TokenType.String && token[1].endsWith("\\") && details && "value" in details) {
|
|
2006
|
+
const quote = token[1][0] ?? "\"";
|
|
2007
|
+
return `${quote}${escapedString(`${details.value}`, quote)}${quote}`;
|
|
2008
|
+
}
|
|
2009
|
+
return token[1];
|
|
2010
|
+
}
|
|
2011
|
+
function recoverTokenText(input) {
|
|
2012
|
+
let parseError = false;
|
|
2013
|
+
const tokens = (0, _csstools_css_tokenizer.tokenize)({ css: input }, { onParseError: () => {
|
|
2014
|
+
parseError = true;
|
|
2015
|
+
} });
|
|
2016
|
+
const stack = [];
|
|
2017
|
+
let retained = "";
|
|
2018
|
+
let closed = "";
|
|
2019
|
+
let removedComment = false;
|
|
2020
|
+
for (const token of tokens) {
|
|
2021
|
+
if (token[0] === _csstools_css_tokenizer.TokenType.EOF) continue;
|
|
2022
|
+
if (token[0] === _csstools_css_tokenizer.TokenType.Comment) {
|
|
2023
|
+
removedComment = true;
|
|
2024
|
+
continue;
|
|
2025
|
+
}
|
|
2026
|
+
const text = retainedToken(token);
|
|
2027
|
+
retained += text;
|
|
2028
|
+
closed += text;
|
|
2029
|
+
const closing = closingToken.get(token[0]);
|
|
2030
|
+
if (closing) {
|
|
2031
|
+
stack.push(closing);
|
|
2032
|
+
continue;
|
|
2033
|
+
}
|
|
2034
|
+
if (stack.at(-1)?.token === token[0]) stack.pop();
|
|
2035
|
+
if (token[0] === _csstools_css_tokenizer.TokenType.URL && !text.endsWith(")")) closed += ")";
|
|
2036
|
+
if (token[0] !== _csstools_css_tokenizer.TokenType.String) continue;
|
|
2037
|
+
const quote = token[1][0];
|
|
2038
|
+
if (quote && !text.endsWith(quote)) closed += quote;
|
|
2039
|
+
}
|
|
2040
|
+
for (let index = stack.length - 1; index >= 0; index -= 1) closed += stack[index]?.text ?? "";
|
|
2041
|
+
return {
|
|
2042
|
+
closed: closed.trim(),
|
|
2043
|
+
recovered: parseError || removedComment || stack.length > 0,
|
|
2044
|
+
retained: retained.trim()
|
|
2045
|
+
};
|
|
2046
|
+
}
|
|
2047
|
+
/** Produces browser-facing text without changing acceptance or reparsable output. */
|
|
2048
|
+
function serializeObservableValue({ name, input, safeValue, category }) {
|
|
2049
|
+
const recovered = recoverTokenText(input.trim());
|
|
2050
|
+
if (category !== "typed") return recovered.retained;
|
|
2051
|
+
if (name === "font-family") return safeValue;
|
|
2052
|
+
const oracle = new cssstyle.CSSStyleDeclaration();
|
|
2053
|
+
oracle.setProperty(name, recovered.closed);
|
|
2054
|
+
const canonicalValue = oracle.getPropertyValue(name);
|
|
2055
|
+
if (canonicalValue !== "") return canonicalValue;
|
|
2056
|
+
return recovered.recovered ? recovered.closed : input.trim();
|
|
2057
|
+
}
|
|
2058
|
+
//#endregion
|
|
1978
2059
|
//#region src/internal/value-gate.ts
|
|
1979
2060
|
const encoder$1 = new TextEncoder();
|
|
1980
2061
|
const decoder$1 = new TextDecoder();
|
|
@@ -2285,7 +2366,14 @@ function parsePropertyValue(name, observableValue) {
|
|
|
2285
2366
|
if (css_tree.ident.decode(serializedDeclaration.property) !== name) return null;
|
|
2286
2367
|
const valueLocation = serializedDeclaration.value.loc;
|
|
2287
2368
|
const safeValue = valueLocation ? serialized.slice(valueLocation.start.offset, valueLocation.end.offset) : css_tree.generate(serializedDeclaration.value);
|
|
2288
|
-
const
|
|
2369
|
+
const observableCategory = name.startsWith("--") ? "custom" : pendingSubstitution ? "pending-substitution" : "typed";
|
|
2370
|
+
const serializedObservableValue = serializeObservableValue({
|
|
2371
|
+
name,
|
|
2372
|
+
input: observableValue,
|
|
2373
|
+
safeValue: safeValue.trim(),
|
|
2374
|
+
category: observableCategory
|
|
2375
|
+
});
|
|
2376
|
+
const trimmedObservableValue = canonicalizeBrowserValue(name, observableCategory === "typed" ? canonicalizeLeadingDecimal(serializedObservableValue) : serializedObservableValue);
|
|
2289
2377
|
const normalizedSafeValue = canonicalizeBrowserValue(name, safeValue.trim());
|
|
2290
2378
|
return {
|
|
2291
2379
|
observableValue: trimmedObservableValue === "0" && zeroLengthProperties.has(name) ? "0px" : trimmedObservableValue,
|
|
@@ -2541,6 +2629,28 @@ function normalizeConditionalPrelude(name, value) {
|
|
|
2541
2629
|
return value;
|
|
2542
2630
|
}
|
|
2543
2631
|
}
|
|
2632
|
+
function parseContainerPrelude(value) {
|
|
2633
|
+
try {
|
|
2634
|
+
const parsed = css_tree.parse(`@container ${value} {}`);
|
|
2635
|
+
const rule = parsed.type === "StyleSheet" ? parsed.children.first : null;
|
|
2636
|
+
if (rule?.type !== "Atrule" || rule.name.toLowerCase() !== "container") return null;
|
|
2637
|
+
if (rule.prelude?.type !== "AtrulePrelude") return null;
|
|
2638
|
+
const children = rule.prelude.children.toArray();
|
|
2639
|
+
const first = children[0];
|
|
2640
|
+
const name = first?.type === "Identifier" ? first.name : "";
|
|
2641
|
+
const queryNodes = name === "" ? children : children.slice(1);
|
|
2642
|
+
if (queryNodes.length === 0) return null;
|
|
2643
|
+
const query = queryNodes.map((node) => css_tree.generate(node)).join(" ").replace(/:\s*/g, ": ").replace(/\s*(<=|>=|=|<|>)\s*/g, " $1 ");
|
|
2644
|
+
if (query === "") return null;
|
|
2645
|
+
return {
|
|
2646
|
+
conditionText: name === "" ? query : `${name} ${query}`,
|
|
2647
|
+
name,
|
|
2648
|
+
query
|
|
2649
|
+
};
|
|
2650
|
+
} catch {
|
|
2651
|
+
return null;
|
|
2652
|
+
}
|
|
2653
|
+
}
|
|
2544
2654
|
function splitMediaQueries(value) {
|
|
2545
2655
|
const queries = [];
|
|
2546
2656
|
let start = 0;
|
|
@@ -2697,11 +2807,11 @@ var CSSContainerRule = class extends CSSConditionRule {
|
|
|
2697
2807
|
containerName;
|
|
2698
2808
|
containerQuery;
|
|
2699
2809
|
constructor(conditionText) {
|
|
2700
|
-
const
|
|
2810
|
+
const parsed = parseContainerPrelude(conditionText);
|
|
2811
|
+
const normalizedCondition = parsed?.conditionText ?? conditionText.trim();
|
|
2701
2812
|
super(0, normalizedCondition);
|
|
2702
|
-
|
|
2703
|
-
this.
|
|
2704
|
-
this.containerQuery = openingParenthesis === -1 ? normalizedCondition : normalizedCondition.slice(openingParenthesis).trim();
|
|
2813
|
+
this.containerName = parsed?.name ?? "";
|
|
2814
|
+
this.containerQuery = parsed?.query ?? normalizedCondition;
|
|
2705
2815
|
lockOwnProperties(this, "containerName", "containerQuery");
|
|
2706
2816
|
}
|
|
2707
2817
|
get cssText() {
|