sheetom 0.1.0-rc.10 → 0.1.0-rc.11
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 +7 -0
- package/dist/index.cjs +106 -20
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +32 -6
- package/dist/index.d.ts +32 -6
- package/dist/index.js +106 -21
- package/dist/index.js.map +1 -1
- package/docs/api.md +11 -6
- package/engine-abi.json +3 -3
- package/package.json +14 -14
package/README.md
CHANGED
|
@@ -119,6 +119,13 @@ sheet.serialize();
|
|
|
119
119
|
| `style.cssText` | Browser-compatible declaration text, including measured recovery quirks. |
|
|
120
120
|
| `rule.cssText` | Browser-compatible text for the current rule. |
|
|
121
121
|
| `sheet.serialize()` | Deterministic, reparsable CSS for a complete stylesheet. |
|
|
122
|
+
| `sheet.serializeStrict()` | Exact-only output that rejects CSSOM states CSS text cannot represent faithfully. |
|
|
123
|
+
|
|
124
|
+
Pending-substitution shorthands survive later longhand edits in reparsable
|
|
125
|
+
output. If an imperative priority combination cannot be represented exactly,
|
|
126
|
+
`serialize()` emits a local best-effort result and an opt-in structured
|
|
127
|
+
diagnostic rather than blocking the whole stylesheet. Use `serializeStrict()`
|
|
128
|
+
when approximation is unacceptable.
|
|
122
129
|
|
|
123
130
|
Invalid property values and priorities are atomic no-ops: the previous state
|
|
124
131
|
survives unchanged. Static shorthands are stored as canonical longhands, so
|
package/dist/index.cjs
CHANGED
|
@@ -28,9 +28,9 @@ let node_url = require("node:url");
|
|
|
28
28
|
//#endregion
|
|
29
29
|
//#region src/internal/engine-binding.ts
|
|
30
30
|
const expectedEngineAbiIdentity = Object.freeze({
|
|
31
|
-
abiVersion:
|
|
32
|
-
sheetomVersion: "0.1.0-rc.
|
|
33
|
-
syntaxEngineSetSha256: "
|
|
31
|
+
abiVersion: 3,
|
|
32
|
+
sheetomVersion: "0.1.0-rc.11",
|
|
33
|
+
syntaxEngineSetSha256: "8c274f05005d475082dbc0ec0c3b6056cb78043f008ed4c813f49fc5a8a2b250"
|
|
34
34
|
});
|
|
35
35
|
var SheetOMEngineBindingError = class extends Error {
|
|
36
36
|
code;
|
|
@@ -114,16 +114,47 @@ function limitValue(value, name, fallback, maximum = maximumUnsignedLong) {
|
|
|
114
114
|
throw new RangeError(`${name} must be an integer between 0 and ${maximum}`);
|
|
115
115
|
}
|
|
116
116
|
//#endregion
|
|
117
|
+
//#region src/serialization-error.ts
|
|
118
|
+
/** A reparsable stylesheet cannot faithfully represent the current CSSOM state. */
|
|
119
|
+
var SheetOMSerializationError = class extends Error {
|
|
120
|
+
code;
|
|
121
|
+
shorthand;
|
|
122
|
+
conflictingLonghands;
|
|
123
|
+
constructor(shorthand, conflictingLonghands, options) {
|
|
124
|
+
super(`SheetOM cannot serialize the pending ${shorthand} shorthand without changing its semantics.`, options);
|
|
125
|
+
this.name = "SheetOMSerializationError";
|
|
126
|
+
this.code = "UNREPRESENTABLE_PENDING_SHORTHAND";
|
|
127
|
+
this.shorthand = shorthand;
|
|
128
|
+
this.conflictingLonghands = Object.freeze([...conflictingLonghands]);
|
|
129
|
+
}
|
|
130
|
+
};
|
|
131
|
+
const unrepresentablePendingShorthandPattern = /^SHEETOM_UNREPRESENTABLE_PENDING_SHORTHAND: shorthand=([^;]+); conflicting=([a-z0-9,-]+)$/u;
|
|
132
|
+
function serializationErrorFrom(error) {
|
|
133
|
+
if (!(error instanceof Error)) return null;
|
|
134
|
+
const match = unrepresentablePendingShorthandPattern.exec(error.message);
|
|
135
|
+
if (!match) return null;
|
|
136
|
+
const shorthand = match[1];
|
|
137
|
+
const conflicts = match[2];
|
|
138
|
+
if (!shorthand || !conflicts) return null;
|
|
139
|
+
return new SheetOMSerializationError(shorthand, conflicts.split(","), { cause: error });
|
|
140
|
+
}
|
|
141
|
+
function rethrowSerializationError(error) {
|
|
142
|
+
const serializationError = serializationErrorFrom(error);
|
|
143
|
+
if (serializationError) throw serializationError;
|
|
144
|
+
}
|
|
145
|
+
//#endregion
|
|
117
146
|
//#region src/internal/native-declaration-block.ts
|
|
118
147
|
/** Thin JS ownership boundary around the Rust declaration state machine. */
|
|
119
148
|
var NativeDeclarationBlock = class {
|
|
120
149
|
#state;
|
|
121
150
|
#reportDiagnostic;
|
|
151
|
+
#reportSerializationDiagnostic;
|
|
122
152
|
#reservedNestingDepth;
|
|
123
153
|
#observableCache;
|
|
124
154
|
#serializationCache = /* @__PURE__ */ new Map();
|
|
125
|
-
constructor(reportDiagnostic, context = "style", resourceBudget = defaultResourceBudget, reservedNestingDepth = () => 0) {
|
|
155
|
+
constructor(reportDiagnostic, reportSerializationDiagnostic, context = "style", resourceBudget = defaultResourceBudget, reservedNestingDepth = () => 0) {
|
|
126
156
|
this.#reportDiagnostic = reportDiagnostic;
|
|
157
|
+
this.#reportSerializationDiagnostic = reportSerializationDiagnostic;
|
|
127
158
|
this.#reservedNestingDepth = reservedNestingDepth;
|
|
128
159
|
this.#state = engineBinding.createDeclarationState(context, ...nativeBudgetArguments(resourceBudget));
|
|
129
160
|
}
|
|
@@ -223,13 +254,27 @@ var NativeDeclarationBlock = class {
|
|
|
223
254
|
}
|
|
224
255
|
this.#invalidateSerialization();
|
|
225
256
|
}
|
|
226
|
-
serialize(safe, indent, separator) {
|
|
227
|
-
const cacheKey = `${safe ? "1" : "0"}\0${indent}\0${separator}`;
|
|
257
|
+
serialize(safe, indent, separator, strict = false) {
|
|
258
|
+
const cacheKey = `${safe ? "1" : "0"}${strict ? "1" : "0"}\0${indent}\0${separator}`;
|
|
228
259
|
const cached = this.#serializationCache.get(cacheKey);
|
|
229
|
-
if (cached !== void 0)
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
260
|
+
if (cached !== void 0) {
|
|
261
|
+
if (typeof cached === "string") return cached;
|
|
262
|
+
this.#reportSerializationIssues(cached.issues);
|
|
263
|
+
return cached.serialized;
|
|
264
|
+
}
|
|
265
|
+
let result;
|
|
266
|
+
try {
|
|
267
|
+
if (strict) result = this.#state.serializeFormatted(safe, indent, separator);
|
|
268
|
+
else result = parseResilientSerializationResult(this.#state.serializeFormattedResilient(safe, indent, separator));
|
|
269
|
+
} catch (error) {
|
|
270
|
+
rethrowResourceBudgetError(error);
|
|
271
|
+
rethrowSerializationError(error);
|
|
272
|
+
throw error;
|
|
273
|
+
}
|
|
274
|
+
this.#serializationCache.set(cacheKey, result);
|
|
275
|
+
if (typeof result === "string") return result;
|
|
276
|
+
this.#reportSerializationIssues(result.issues);
|
|
277
|
+
return result.serialized;
|
|
233
278
|
}
|
|
234
279
|
#invalidateSerialization() {
|
|
235
280
|
this.#observableCache = void 0;
|
|
@@ -240,7 +285,29 @@ var NativeDeclarationBlock = class {
|
|
|
240
285
|
const [code, input] = outcome === "invalid-priority" ? ["INVALID_PRIORITY", priority] : outcome === "unsupported-shorthand" ? ["UNSUPPORTED_SHORTHAND_VALUE", value] : ["INVALID_PROPERTY_VALUE", value];
|
|
241
286
|
return this.#reportDiagnostic(code, normalizedName, input);
|
|
242
287
|
}
|
|
288
|
+
#reportSerializationIssues(issues) {
|
|
289
|
+
for (const issue of issues) this.#reportSerializationDiagnostic(issue.shorthand, issue.conflictingLonghands);
|
|
290
|
+
}
|
|
243
291
|
};
|
|
292
|
+
function parseResilientSerializationResult(output) {
|
|
293
|
+
const serialized = output[0];
|
|
294
|
+
if (serialized === void 0 || output.length % 2 === 0) throw new Error("SheetOM engine returned an invalid resilient serialization result.");
|
|
295
|
+
if (output.length === 1) return serialized;
|
|
296
|
+
const issues = [];
|
|
297
|
+
for (let index = 1; index < output.length; index += 2) {
|
|
298
|
+
const shorthand = output[index];
|
|
299
|
+
const conflicts = output[index + 1];
|
|
300
|
+
if (!shorthand || !conflicts) throw new Error("SheetOM engine returned an incomplete resilient serialization issue.");
|
|
301
|
+
issues.push({
|
|
302
|
+
shorthand,
|
|
303
|
+
conflictingLonghands: Object.freeze(conflicts.split(","))
|
|
304
|
+
});
|
|
305
|
+
}
|
|
306
|
+
return {
|
|
307
|
+
serialized,
|
|
308
|
+
issues
|
|
309
|
+
};
|
|
310
|
+
}
|
|
244
311
|
function isEngineMutationOutcome(value) {
|
|
245
312
|
return value === "invalid-name" || value === "invalid-priority" || value === "invalid-value" || value === "unsupported-shorthand";
|
|
246
313
|
}
|
|
@@ -562,7 +629,8 @@ const ruleTree = new RuleTree((rule, parentRule, parentStyleSheet) => {
|
|
|
562
629
|
parentStyleSheet
|
|
563
630
|
});
|
|
564
631
|
}, (rule) => rule.parentStyleSheet);
|
|
565
|
-
const safeSerializer = new Serializer(describeRuleSafe);
|
|
632
|
+
const safeSerializer = new Serializer((rule) => describeRuleSafe(rule, false));
|
|
633
|
+
const strictSerializer = new Serializer((rule) => describeRuleSafe(rule, true));
|
|
566
634
|
let activeConstructionResourceBudget = defaultResourceBudget;
|
|
567
635
|
function constructWithResourceBudget(factory, resourceBudget) {
|
|
568
636
|
const previous = activeConstructionResourceBudget;
|
|
@@ -1227,7 +1295,20 @@ var CSSStyleDeclaration = class {
|
|
|
1227
1295
|
(ruleDiagnostics.get(this.parentRule) ?? ignoreDiagnostic)(diagnostic);
|
|
1228
1296
|
return diagnostic;
|
|
1229
1297
|
};
|
|
1230
|
-
|
|
1298
|
+
const reportSerializationDiagnostic = (shorthand, conflictingLonghands) => {
|
|
1299
|
+
const diagnostic = {
|
|
1300
|
+
code: "UNREPRESENTABLE_PENDING_SHORTHAND",
|
|
1301
|
+
severity: "warning",
|
|
1302
|
+
operation: "serialize",
|
|
1303
|
+
message: `The pending ${shorthand} shorthand required a best-effort recovery during serialization.`,
|
|
1304
|
+
property: shorthand,
|
|
1305
|
+
input: "",
|
|
1306
|
+
location: null,
|
|
1307
|
+
conflictingLonghands: Object.freeze([...conflictingLonghands])
|
|
1308
|
+
};
|
|
1309
|
+
(ruleDiagnostics.get(this.parentRule) ?? ignoreDiagnostic)(diagnostic);
|
|
1310
|
+
};
|
|
1311
|
+
this.#block = new NativeDeclarationBlock(reportDeclarationDiagnostic, reportSerializationDiagnostic, parentRule instanceof CSSFontFaceRule ? "font-face" : parentRule instanceof CSSFunctionDeclarations ? "function" : "style", ruleResourceBudgets.get(parentRule) ?? defaultResourceBudget, () => declarationSerializationDepth(parentRule));
|
|
1231
1312
|
return new Proxy(this, {
|
|
1232
1313
|
get(target, property) {
|
|
1233
1314
|
if (typeof property === "string" && arrayIndexPattern.test(property)) return target.item(Number(property)) || void 0;
|
|
@@ -1305,8 +1386,8 @@ var CSSStyleDeclaration = class {
|
|
|
1305
1386
|
return this.#block.removeProperty(`${name}`);
|
|
1306
1387
|
}
|
|
1307
1388
|
/** @internal */
|
|
1308
|
-
serializeSafe(indent) {
|
|
1309
|
-
return this.#block.serialize(true, indent, "\n");
|
|
1389
|
+
serializeSafe(indent, strict = false) {
|
|
1390
|
+
return this.#block.serialize(true, indent, "\n", strict);
|
|
1310
1391
|
}
|
|
1311
1392
|
};
|
|
1312
1393
|
/** A live `@font-face` descriptor rule. */
|
|
@@ -1795,23 +1876,23 @@ function replaceGroupingRules(group, rules) {
|
|
|
1795
1876
|
function attachRuleTree(rule, parentRule, parentStyleSheet) {
|
|
1796
1877
|
ruleTree.attach(rule, parentRule, parentStyleSheet);
|
|
1797
1878
|
}
|
|
1798
|
-
function describeRuleSafe(rule) {
|
|
1879
|
+
function describeRuleSafe(rule, strict) {
|
|
1799
1880
|
if (rule instanceof CSSStyleRule) return {
|
|
1800
1881
|
kind: "block",
|
|
1801
1882
|
header: rule.selectorText,
|
|
1802
|
-
declarations: rule.style.serializeSafe(" "),
|
|
1883
|
+
declarations: rule.style.serializeSafe(" ", strict),
|
|
1803
1884
|
children: ruleTree.children(rule)
|
|
1804
1885
|
};
|
|
1805
1886
|
if (rule instanceof CSSNestedDeclarations || rule instanceof CSSFunctionDeclarations) return {
|
|
1806
1887
|
kind: "declarations",
|
|
1807
|
-
declarations: rule.style.serializeSafe("")
|
|
1888
|
+
declarations: rule.style.serializeSafe("", strict)
|
|
1808
1889
|
};
|
|
1809
1890
|
if (rule instanceof CSSFontFaceRule || rule instanceof CSSMarginRule || rule instanceof CSSPositionTryRule || rule instanceof CSSKeyframeRule) {
|
|
1810
1891
|
const blockIndex = rule.cssText.indexOf("{");
|
|
1811
1892
|
return {
|
|
1812
1893
|
kind: "block",
|
|
1813
1894
|
header: rule.cssText.slice(0, blockIndex).trimEnd(),
|
|
1814
|
-
declarations: rule.style.serializeSafe(" ")
|
|
1895
|
+
declarations: rule.style.serializeSafe(" ", strict)
|
|
1815
1896
|
};
|
|
1816
1897
|
}
|
|
1817
1898
|
if (rule instanceof CSSKeyframesRule) return {
|
|
@@ -1822,7 +1903,7 @@ function describeRuleSafe(rule) {
|
|
|
1822
1903
|
if (rule instanceof CSSPageRule) return {
|
|
1823
1904
|
kind: "block",
|
|
1824
1905
|
header: `@page${rule.selectorText === "" ? "" : ` ${rule.selectorText}`}`,
|
|
1825
|
-
declarations: rule.style.serializeSafe(" "),
|
|
1906
|
+
declarations: rule.style.serializeSafe(" ", strict),
|
|
1826
1907
|
children: ruleTree.children(rule)
|
|
1827
1908
|
};
|
|
1828
1909
|
if (!(rule instanceof CSSGroupingRule)) return {
|
|
@@ -2219,7 +2300,11 @@ var CSSStyleSheet = class {
|
|
|
2219
2300
|
serialize() {
|
|
2220
2301
|
return safeSerializer.serializeMany(this.#rules);
|
|
2221
2302
|
}
|
|
2222
|
-
/**
|
|
2303
|
+
/** Serialize current state only when its CSS semantics can be preserved exactly. */
|
|
2304
|
+
serializeStrict() {
|
|
2305
|
+
return strictSerializer.serializeMany(this.#rules);
|
|
2306
|
+
}
|
|
2307
|
+
/** Drain opt-in mutation and serialization diagnostics without affecting CSSOM behavior. */
|
|
2223
2308
|
takeDiagnostics() {
|
|
2224
2309
|
if (!this.#diagnostics) return [];
|
|
2225
2310
|
return this.#diagnostics.splice(0);
|
|
@@ -2270,6 +2355,7 @@ exports.CSSStyleSheet = CSSStyleSheet;
|
|
|
2270
2355
|
exports.CSSSupportsRule = CSSSupportsRule;
|
|
2271
2356
|
exports.CSSViewTransitionRule = CSSViewTransitionRule;
|
|
2272
2357
|
exports.MediaList = MediaList;
|
|
2358
|
+
exports.SheetOMSerializationError = SheetOMSerializationError;
|
|
2273
2359
|
exports.parseStyleSheet = parseStyleSheet;
|
|
2274
2360
|
|
|
2275
2361
|
//# sourceMappingURL=index.cjs.map
|