@csszyx/dynamic 0.9.10 → 0.10.1

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/dist/index.d.mts CHANGED
@@ -28,6 +28,51 @@ interface CSSManifest {
28
28
  mangleMap?: Record<string, string>;
29
29
  }
30
30
 
31
+ /**
32
+ * Purify an UNTRUSTED sz object before it reaches the runtime CSS pipeline.
33
+ *
34
+ * csszyx's `dynamic()` / `useSz` accept sz objects that an app may build from
35
+ * untrusted data (a JSON-driven UI, a CMS schema). Such input controls both the
36
+ * KEYS and the VALUES of the object, which opens three holes the rest of the
37
+ * stack only partially closes: an unknown key produces dead CSS or, via a key
38
+ * like `__proto__`, pollutes the prototype; an unsafe value injects an extra CSS
39
+ * declaration (UI redress / exfil) into the same rule; deep nesting overflows
40
+ * the stack. `purifySz` is the opt-in boundary that drops all three.
41
+ *
42
+ * It is allowlist-based: only keys the compiler actually understands
43
+ * (`SZ_KEY_CATEGORY`, generated from the compiler tables) and valid variant keys
44
+ * survive; every string value must pass {@link isSafeCssValue}; forbidden keys
45
+ * are skipped; depth is bounded by the shared `MAX_SZ_DEPTH`. In `strict` mode
46
+ * (default) it additionally drops values carrying `url()` / `image-set()` /
47
+ * `@import` / `expression()` — vectors the authored-path emitter intentionally
48
+ * allows but which are exfil/legacy risks for untrusted input.
49
+ *
50
+ * Pure, framework-agnostic. Compiled/authored sz does NOT need this — it is for
51
+ * the untrusted runtime boundary only.
52
+ */
53
+
54
+ /** Options controlling `purifySz` strictness and drop reporting. */
55
+ interface PurifySzOptions {
56
+ /**
57
+ * When true (default), also reject values containing `url()`/`image-set()`/
58
+ * `@import`/`expression()`. These are legitimate for AUTHORED sz but are
59
+ * exfiltration / legacy vectors when the value is attacker-controlled.
60
+ */
61
+ strict?: boolean;
62
+ /** Called for each dropped key/value with its dotted path and a reason. */
63
+ onDrop?: (path: string, reason: string) => void;
64
+ }
65
+ /**
66
+ * Return a cleaned copy of `sz` safe to pass to `dynamic()` / `useSz` from
67
+ * untrusted input. Drops unknown keys, unsafe values, and prototype-polluting
68
+ * keys; bounds nesting depth. See {@link PurifySzOptions}.
69
+ *
70
+ * @param sz - the untrusted sz object.
71
+ * @param options - strictness + drop reporting.
72
+ * @returns a new sz object containing only allowed keys and safe values.
73
+ */
74
+ declare function purifySz(sz: SzObject, options?: PurifySzOptions): SzObject;
75
+
31
76
  /**
32
77
  * @csszyx/dynamic — Runtime CSS injection with delta check.
33
78
  *
@@ -88,5 +133,5 @@ declare function preloadManifest(url?: string): Promise<void>;
88
133
  */
89
134
  declare function cleanup(): void;
90
135
 
91
- export { cleanup, dynamic, preloadManifest };
92
- export type { CSSManifest, Tier };
136
+ export { cleanup, dynamic, preloadManifest, purifySz };
137
+ export type { CSSManifest, PurifySzOptions, Tier };
package/dist/index.mjs CHANGED
@@ -1,2 +1,2 @@
1
1
  import '@csszyx/compiler/browser';
2
- export { a as cleanup, d as dynamic, b as preloadManifest } from './shared/dynamic.DJAOP9e8.mjs';
2
+ export { a as cleanup, d as dynamic, b as preloadManifest, e as purifySz } from './shared/dynamic.CZNnPnjy.mjs';
package/dist/react.mjs CHANGED
@@ -1,5 +1,5 @@
1
1
  import { createContext, useEffect, createElement, useContext, useCallback } from 'react';
2
- import { d as dynamic, p as preloadManifest, c as cleanup, r as resetManifest, s as setManifestUrl } from './shared/dynamic.DJAOP9e8.mjs';
2
+ import { d as dynamic, p as preloadManifest, c as cleanup, r as resetManifest, s as setManifestUrl } from './shared/dynamic.CZNnPnjy.mjs';
3
3
  import '@csszyx/compiler/browser';
4
4
 
5
5
  const CsszyxContext = createContext({
@@ -1,5 +1,83 @@
1
- import { transform } from '@csszyx/compiler/browser';
1
+ import { MAX_SZ_DEPTH, SzDepthError, isForbiddenSzKey, KNOWN_VARIANTS, getVariantPrefix, SPECIAL_VARIANTS, transform } from '@csszyx/compiler/browser';
2
2
 
3
+ function isSafeCssValue(value) {
4
+ let quote = null;
5
+ let parenDepth = 0;
6
+ for (let i = 0; i < value.length; i++) {
7
+ const ch = value[i];
8
+ if (value.charCodeAt(i) < 32) {
9
+ return false;
10
+ }
11
+ if (quote !== null) {
12
+ if (ch === "\\") {
13
+ i++;
14
+ continue;
15
+ }
16
+ if (ch === quote) {
17
+ quote = null;
18
+ }
19
+ continue;
20
+ }
21
+ switch (ch) {
22
+ case '"':
23
+ case "'":
24
+ quote = ch;
25
+ break;
26
+ case "(":
27
+ parenDepth++;
28
+ break;
29
+ case ")":
30
+ if (parenDepth > 0) {
31
+ parenDepth--;
32
+ }
33
+ break;
34
+ case "{":
35
+ case "}":
36
+ case "<":
37
+ case ">":
38
+ return false;
39
+ case ";":
40
+ if (parenDepth === 0) {
41
+ return false;
42
+ }
43
+ break;
44
+ }
45
+ }
46
+ return quote === null && parenDepth === 0;
47
+ }
48
+ function isSafeCssPropertyName(name) {
49
+ return /^(?:--[\w-]+|-?[a-z][a-z0-9-]*)$/i.test(name);
50
+ }
51
+ function isUtilityArbitrarySafe(utility) {
52
+ const open = utility.indexOf("[");
53
+ if (open === -1) {
54
+ return true;
55
+ }
56
+ const close = utility.lastIndexOf("]");
57
+ if (close <= open) {
58
+ return true;
59
+ }
60
+ const inner = utility.slice(open + 1, close);
61
+ if (utility.charCodeAt(0) === 91 && utility.charCodeAt(utility.length - 1) === 93 && inner.includes(":")) {
62
+ const colon = inner.indexOf(":");
63
+ return isSafeCssPropertyName(inner.slice(0, colon)) && isSafeCssValue(inner.slice(colon + 1));
64
+ }
65
+ return isSafeCssValue(inner);
66
+ }
67
+
68
+ let warnedUnsafeArbitrary = false;
69
+ function warnUnsafeArbitrary(utility) {
70
+ if (warnedUnsafeArbitrary) {
71
+ return;
72
+ }
73
+ if (typeof process !== "undefined" && process.env?.NODE_ENV === "production") {
74
+ return;
75
+ }
76
+ warnedUnsafeArbitrary = true;
77
+ console.warn(
78
+ `[csszyx] dropped an arbitrary value that could inject CSS: "${utility}". Arbitrary values from untrusted data are not emitted at runtime.`
79
+ );
80
+ }
3
81
  const BREAKPOINTS = {
4
82
  sm: "40rem",
5
83
  md: "48rem",
@@ -553,6 +631,10 @@ function parseVariants(cls) {
553
631
  return { tier, pseudoSuffix, selectorPrefix, utility };
554
632
  }
555
633
  function generateDeclarations(utility) {
634
+ if (!isUtilityArbitrarySafe(utility)) {
635
+ warnUnsafeArbitrary(utility);
636
+ return "";
637
+ }
556
638
  if (utility in KEYWORD_RULES) {
557
639
  return KEYWORD_RULES[utility];
558
640
  }
@@ -1110,6 +1192,421 @@ function resetManifest() {
1110
1192
 
1111
1193
  const isServer = typeof document === "undefined";
1112
1194
 
1195
+ const SZ_KEY_CATEGORY = /* @__PURE__ */ new Map([
1196
+ ["accent", "COLOR"],
1197
+ ["align", "PASSTHROUGH"],
1198
+ ["animate", "PASSTHROUGH"],
1199
+ ["animationDelay", "DURATION"],
1200
+ ["appearance", "PASSTHROUGH"],
1201
+ ["aspect", "PASSTHROUGH"],
1202
+ ["autoCols", "PASSTHROUGH"],
1203
+ ["autoRows", "PASSTHROUGH"],
1204
+ ["backdropBlur", "PASSTHROUGH"],
1205
+ ["backdropBrightness", "UNITLESS"],
1206
+ ["backdropContrast", "UNITLESS"],
1207
+ ["backdropFilter", "PASSTHROUGH"],
1208
+ ["backdropGrayscale", "PASSTHROUGH"],
1209
+ ["backdropHueRotate", "UNITLESS"],
1210
+ ["backdropInvert", "PASSTHROUGH"],
1211
+ ["backdropOpacity", "UNITLESS"],
1212
+ ["backdropSaturate", "UNITLESS"],
1213
+ ["backdropSepia", "PASSTHROUGH"],
1214
+ ["backface", "PASSTHROUGH"],
1215
+ ["basis", "SPACING"],
1216
+ ["bg", "COLOR"],
1217
+ ["bgAttach", "PASSTHROUGH"],
1218
+ ["bgBlend", "PASSTHROUGH"],
1219
+ ["bgClip", "PASSTHROUGH"],
1220
+ ["bgImg", "PASSTHROUGH"],
1221
+ ["bgOrigin", "PASSTHROUGH"],
1222
+ ["bgPos", "PASSTHROUGH"],
1223
+ ["bgRepeat", "PASSTHROUGH"],
1224
+ ["bgSize", "PASSTHROUGH"],
1225
+ ["blockSize", "SPACING"],
1226
+ ["blur", "PASSTHROUGH"],
1227
+ ["border", "UNITLESS"],
1228
+ ["borderB", "UNITLESS"],
1229
+ ["borderBColor", "PASSTHROUGH"],
1230
+ ["borderBe", "UNITLESS"],
1231
+ ["borderBs", "UNITLESS"],
1232
+ ["borderCollapse", "PASSTHROUGH"],
1233
+ ["borderColor", "COLOR"],
1234
+ ["borderE", "UNITLESS"],
1235
+ ["borderL", "UNITLESS"],
1236
+ ["borderLColor", "PASSTHROUGH"],
1237
+ ["borderR", "UNITLESS"],
1238
+ ["borderRColor", "PASSTHROUGH"],
1239
+ ["borderS", "UNITLESS"],
1240
+ ["borderSpacing", "SPACING"],
1241
+ ["borderSpacingX", "SPACING"],
1242
+ ["borderSpacingY", "SPACING"],
1243
+ ["borderStyle", "PASSTHROUGH"],
1244
+ ["borderT", "UNITLESS"],
1245
+ ["borderTColor", "PASSTHROUGH"],
1246
+ ["borderX", "UNITLESS"],
1247
+ ["borderXColor", "PASSTHROUGH"],
1248
+ ["borderY", "UNITLESS"],
1249
+ ["borderYColor", "PASSTHROUGH"],
1250
+ ["bottom", "SPACING"],
1251
+ ["box", "PASSTHROUGH"],
1252
+ ["boxDecoration", "PASSTHROUGH"],
1253
+ ["break", "PASSTHROUGH"],
1254
+ ["breakAfter", "PASSTHROUGH"],
1255
+ ["breakBefore", "PASSTHROUGH"],
1256
+ ["breakInside", "PASSTHROUGH"],
1257
+ ["brightness", "UNITLESS"],
1258
+ ["caption", "PASSTHROUGH"],
1259
+ ["caret", "COLOR"],
1260
+ ["clear", "PASSTHROUGH"],
1261
+ ["col", "UNITLESS"],
1262
+ ["colEnd", "UNITLESS"],
1263
+ ["colSpan", "UNITLESS"],
1264
+ ["colStart", "UNITLESS"],
1265
+ ["color", "COLOR"],
1266
+ ["columns", "UNITLESS"],
1267
+ ["container", "PASSTHROUGH"],
1268
+ ["content", "PASSTHROUGH"],
1269
+ ["contrast", "UNITLESS"],
1270
+ ["cursor", "PASSTHROUGH"],
1271
+ ["decoration", "PASSTHROUGH"],
1272
+ ["decorationColor", "COLOR"],
1273
+ ["decorationStyle", "PASSTHROUGH"],
1274
+ ["decorationThickness", "UNITLESS"],
1275
+ ["delay", "DURATION"],
1276
+ ["diagonalFractions", "PASSTHROUGH"],
1277
+ ["display", "PASSTHROUGH"],
1278
+ ["divideColor", "COLOR"],
1279
+ ["divideStyle", "PASSTHROUGH"],
1280
+ ["divideX", "PASSTHROUGH"],
1281
+ ["divideXReverse", "PASSTHROUGH"],
1282
+ ["divideY", "PASSTHROUGH"],
1283
+ ["divideYReverse", "PASSTHROUGH"],
1284
+ ["dropShadow", "PASSTHROUGH"],
1285
+ ["dropShadowColor", "COLOR"],
1286
+ ["duration", "DURATION"],
1287
+ ["ease", "PASSTHROUGH"],
1288
+ ["end", "SPACING"],
1289
+ ["fieldSizing", "PASSTHROUGH"],
1290
+ ["fill", "COLOR"],
1291
+ ["filter", "PASSTHROUGH"],
1292
+ ["flex", "PASSTHROUGH"],
1293
+ ["flexDir", "PASSTHROUGH"],
1294
+ ["flexWrap", "PASSTHROUGH"],
1295
+ ["float", "PASSTHROUGH"],
1296
+ ["fontFamily", "PASSTHROUGH"],
1297
+ ["fontFeatures", "PASSTHROUGH"],
1298
+ ["fontSmoothing", "PASSTHROUGH"],
1299
+ ["fontStretch", "PASSTHROUGH"],
1300
+ ["fontStyle", "PASSTHROUGH"],
1301
+ ["forcedColorAdjust", "PASSTHROUGH"],
1302
+ ["from", "COLOR"],
1303
+ ["gap", "SPACING"],
1304
+ ["gapX", "SPACING"],
1305
+ ["gapY", "SPACING"],
1306
+ ["grayscale", "PASSTHROUGH"],
1307
+ ["gridCols", "UNITLESS"],
1308
+ ["gridFlow", "PASSTHROUGH"],
1309
+ ["gridRows", "UNITLESS"],
1310
+ ["grow", "UNITLESS"],
1311
+ ["h", "SPACING"],
1312
+ ["hueRotate", "UNITLESS"],
1313
+ ["hyphens", "PASSTHROUGH"],
1314
+ ["indent", "SPACING"],
1315
+ ["inlineSize", "SPACING"],
1316
+ ["inset", "SPACING"],
1317
+ ["insetBe", "SPACING"],
1318
+ ["insetBs", "SPACING"],
1319
+ ["insetE", "SPACING"],
1320
+ ["insetRing", "PASSTHROUGH"],
1321
+ ["insetRingColor", "PASSTHROUGH"],
1322
+ ["insetS", "SPACING"],
1323
+ ["insetShadow", "PASSTHROUGH"],
1324
+ ["insetShadowColor", "PASSTHROUGH"],
1325
+ ["insetX", "SPACING"],
1326
+ ["insetY", "SPACING"],
1327
+ ["invert", "PASSTHROUGH"],
1328
+ ["isolation", "PASSTHROUGH"],
1329
+ ["items", "PASSTHROUGH"],
1330
+ ["justify", "PASSTHROUGH"],
1331
+ ["justifyItems", "PASSTHROUGH"],
1332
+ ["justifySelf", "PASSTHROUGH"],
1333
+ ["leading", "UNITLESS"],
1334
+ ["left", "SPACING"],
1335
+ ["lineClamp", "UNITLESS"],
1336
+ ["liningNums", "PASSTHROUGH"],
1337
+ ["list", "PASSTHROUGH"],
1338
+ ["listImg", "PASSTHROUGH"],
1339
+ ["listPos", "PASSTHROUGH"],
1340
+ ["m", "SPACING"],
1341
+ ["mask", "PASSTHROUGH"],
1342
+ ["maskClip", "PASSTHROUGH"],
1343
+ ["maskFrom", "COLOR"],
1344
+ ["maskOrigin", "PASSTHROUGH"],
1345
+ ["maskPos", "PASSTHROUGH"],
1346
+ ["maskRepeat", "PASSTHROUGH"],
1347
+ ["maskShape", "PASSTHROUGH"],
1348
+ ["maskSize", "PASSTHROUGH"],
1349
+ ["maskTo", "COLOR"],
1350
+ ["maskVia", "COLOR"],
1351
+ ["maxBlockSize", "SPACING"],
1352
+ ["maxH", "SPACING"],
1353
+ ["maxInlineSize", "SPACING"],
1354
+ ["maxW", "SPACING"],
1355
+ ["mb", "SPACING"],
1356
+ ["mbe", "SPACING"],
1357
+ ["mbs", "SPACING"],
1358
+ ["me", "SPACING"],
1359
+ ["minBlockSize", "SPACING"],
1360
+ ["minH", "SPACING"],
1361
+ ["minInlineSize", "SPACING"],
1362
+ ["minW", "SPACING"],
1363
+ ["mixBlend", "PASSTHROUGH"],
1364
+ ["ml", "SPACING"],
1365
+ ["mr", "SPACING"],
1366
+ ["ms", "SPACING"],
1367
+ ["mt", "SPACING"],
1368
+ ["mx", "SPACING"],
1369
+ ["my", "SPACING"],
1370
+ ["notSrOnly", "PASSTHROUGH"],
1371
+ ["objectFit", "PASSTHROUGH"],
1372
+ ["objectPos", "PASSTHROUGH"],
1373
+ ["oldstyleNums", "PASSTHROUGH"],
1374
+ ["opacity", "UNITLESS"],
1375
+ ["order", "UNITLESS"],
1376
+ ["ordinal", "PASSTHROUGH"],
1377
+ ["origin", "PASSTHROUGH"],
1378
+ ["outline", "UNITLESS"],
1379
+ ["outlineColor", "COLOR"],
1380
+ ["outlineOffset", "SPACING"],
1381
+ ["outlineStyle", "PASSTHROUGH"],
1382
+ ["overflow", "PASSTHROUGH"],
1383
+ ["overflowX", "PASSTHROUGH"],
1384
+ ["overflowY", "PASSTHROUGH"],
1385
+ ["overscroll", "PASSTHROUGH"],
1386
+ ["overscrollX", "PASSTHROUGH"],
1387
+ ["overscrollY", "PASSTHROUGH"],
1388
+ ["p", "SPACING"],
1389
+ ["pb", "SPACING"],
1390
+ ["pbe", "SPACING"],
1391
+ ["pbs", "SPACING"],
1392
+ ["pe", "SPACING"],
1393
+ ["perspective", "PASSTHROUGH"],
1394
+ ["perspectiveOrigin", "PASSTHROUGH"],
1395
+ ["pl", "SPACING"],
1396
+ ["placeContent", "PASSTHROUGH"],
1397
+ ["placeItems", "PASSTHROUGH"],
1398
+ ["placeSelf", "PASSTHROUGH"],
1399
+ ["pointerEvents", "PASSTHROUGH"],
1400
+ ["position", "PASSTHROUGH"],
1401
+ ["pr", "SPACING"],
1402
+ ["proportionalNums", "PASSTHROUGH"],
1403
+ ["prose", "PASSTHROUGH"],
1404
+ ["proseInvert", "PASSTHROUGH"],
1405
+ ["ps", "SPACING"],
1406
+ ["pt", "SPACING"],
1407
+ ["px", "SPACING"],
1408
+ ["py", "SPACING"],
1409
+ ["resize", "PASSTHROUGH"],
1410
+ ["right", "SPACING"],
1411
+ ["ring", "UNITLESS"],
1412
+ ["ringColor", "COLOR"],
1413
+ ["ringOffset", "SPACING"],
1414
+ ["ringOffsetColor", "COLOR"],
1415
+ ["rotate", "ANGLE"],
1416
+ ["rotateX", "PASSTHROUGH"],
1417
+ ["rotateY", "PASSTHROUGH"],
1418
+ ["rotateZ", "PASSTHROUGH"],
1419
+ ["rounded", "PASSTHROUGH"],
1420
+ ["roundedB", "PASSTHROUGH"],
1421
+ ["roundedBl", "PASSTHROUGH"],
1422
+ ["roundedBr", "PASSTHROUGH"],
1423
+ ["roundedE", "PASSTHROUGH"],
1424
+ ["roundedEe", "PASSTHROUGH"],
1425
+ ["roundedEs", "PASSTHROUGH"],
1426
+ ["roundedL", "PASSTHROUGH"],
1427
+ ["roundedR", "PASSTHROUGH"],
1428
+ ["roundedS", "PASSTHROUGH"],
1429
+ ["roundedSe", "PASSTHROUGH"],
1430
+ ["roundedSs", "PASSTHROUGH"],
1431
+ ["roundedT", "PASSTHROUGH"],
1432
+ ["roundedTl", "PASSTHROUGH"],
1433
+ ["roundedTr", "PASSTHROUGH"],
1434
+ ["row", "UNITLESS"],
1435
+ ["rowEnd", "UNITLESS"],
1436
+ ["rowSpan", "UNITLESS"],
1437
+ ["rowStart", "UNITLESS"],
1438
+ ["saturate", "UNITLESS"],
1439
+ ["scale", "UNITLESS"],
1440
+ ["scaleX", "UNITLESS"],
1441
+ ["scaleY", "UNITLESS"],
1442
+ ["scaleZ", "PASSTHROUGH"],
1443
+ ["scheme", "PASSTHROUGH"],
1444
+ ["scroll", "PASSTHROUGH"],
1445
+ ["scrollM", "SPACING"],
1446
+ ["scrollMb", "SPACING"],
1447
+ ["scrollMbe", "SPACING"],
1448
+ ["scrollMbs", "SPACING"],
1449
+ ["scrollMe", "SPACING"],
1450
+ ["scrollMl", "SPACING"],
1451
+ ["scrollMr", "SPACING"],
1452
+ ["scrollMs", "SPACING"],
1453
+ ["scrollMt", "SPACING"],
1454
+ ["scrollMx", "SPACING"],
1455
+ ["scrollMy", "SPACING"],
1456
+ ["scrollP", "SPACING"],
1457
+ ["scrollPb", "SPACING"],
1458
+ ["scrollPbe", "SPACING"],
1459
+ ["scrollPbs", "SPACING"],
1460
+ ["scrollPe", "SPACING"],
1461
+ ["scrollPl", "SPACING"],
1462
+ ["scrollPr", "SPACING"],
1463
+ ["scrollPs", "SPACING"],
1464
+ ["scrollPt", "SPACING"],
1465
+ ["scrollPx", "SPACING"],
1466
+ ["scrollPy", "SPACING"],
1467
+ ["scrollbar", "PASSTHROUGH"],
1468
+ ["scrollbarGutter", "PASSTHROUGH"],
1469
+ ["scrollbarThumb", "COLOR"],
1470
+ ["scrollbarTrack", "COLOR"],
1471
+ ["select", "PASSTHROUGH"],
1472
+ ["self", "PASSTHROUGH"],
1473
+ ["sepia", "PASSTHROUGH"],
1474
+ ["shadow", "PASSTHROUGH"],
1475
+ ["shadowColor", "COLOR"],
1476
+ ["shrink", "UNITLESS"],
1477
+ ["size", "SPACING"],
1478
+ ["skewX", "ANGLE"],
1479
+ ["skewY", "ANGLE"],
1480
+ ["slashedZero", "PASSTHROUGH"],
1481
+ ["snapAlign", "PASSTHROUGH"],
1482
+ ["snapStop", "PASSTHROUGH"],
1483
+ ["snapType", "PASSTHROUGH"],
1484
+ ["spaceX", "SPACING"],
1485
+ ["spaceXReverse", "PASSTHROUGH"],
1486
+ ["spaceY", "SPACING"],
1487
+ ["spaceYReverse", "PASSTHROUGH"],
1488
+ ["srOnly", "PASSTHROUGH"],
1489
+ ["stackedFractions", "PASSTHROUGH"],
1490
+ ["start", "SPACING"],
1491
+ ["stroke", "COLOR"],
1492
+ ["strokeWidth", "UNITLESS"],
1493
+ ["tabSize", "UNITLESS"],
1494
+ ["tableLayout", "PASSTHROUGH"],
1495
+ ["tabularNums", "PASSTHROUGH"],
1496
+ ["text", "PASSTHROUGH"],
1497
+ ["textAlign", "PASSTHROUGH"],
1498
+ ["textOverflow", "PASSTHROUGH"],
1499
+ ["textShadow", "PASSTHROUGH"],
1500
+ ["textShadowColor", "COLOR"],
1501
+ ["textTransform", "PASSTHROUGH"],
1502
+ ["textWrap", "PASSTHROUGH"],
1503
+ ["to", "COLOR"],
1504
+ ["top", "SPACING"],
1505
+ ["touch", "PASSTHROUGH"],
1506
+ ["tracking", "PASSTHROUGH"],
1507
+ ["transform", "PASSTHROUGH"],
1508
+ ["transformStyle", "PASSTHROUGH"],
1509
+ ["transition", "PASSTHROUGH"],
1510
+ ["transitionBehavior", "PASSTHROUGH"],
1511
+ ["translate", "PASSTHROUGH"],
1512
+ ["translateX", "SPACING"],
1513
+ ["translateY", "SPACING"],
1514
+ ["translateZ", "PASSTHROUGH"],
1515
+ ["truncate", "PASSTHROUGH"],
1516
+ ["underlineOffset", "SPACING"],
1517
+ ["via", "COLOR"],
1518
+ ["visibility", "PASSTHROUGH"],
1519
+ ["w", "SPACING"],
1520
+ ["weight", "PASSTHROUGH"],
1521
+ ["whitespace", "PASSTHROUGH"],
1522
+ ["willChange", "PASSTHROUGH"],
1523
+ ["wrap", "PASSTHROUGH"],
1524
+ ["z", "UNITLESS"],
1525
+ ["zoom", "UNITLESS"]
1526
+ ]);
1527
+
1528
+ const DANGEROUS_VALUE_RE = /url\s*\(|image-set\s*\(|@import|expression\s*\(/i;
1529
+ function isVariantKey(key) {
1530
+ if (KNOWN_VARIANTS.has(key) || KNOWN_VARIANTS.has(getVariantPrefix(key))) {
1531
+ return true;
1532
+ }
1533
+ if (SPECIAL_VARIANTS.has(key) || SPECIAL_VARIANTS.has(getVariantPrefix(key))) {
1534
+ return true;
1535
+ }
1536
+ if (key.startsWith("@")) return true;
1537
+ if (key.startsWith("[") && key.endsWith("]")) return true;
1538
+ if (/^(?:group|peer)-/.test(key)) return true;
1539
+ if (/^[a-z][a-z-]*-\[.*\]$/.test(key)) return true;
1540
+ return false;
1541
+ }
1542
+ function isValueSafe(value, strict) {
1543
+ if (!isSafeCssValue(value)) return false;
1544
+ if (strict && DANGEROUS_VALUE_RE.test(value)) return false;
1545
+ return true;
1546
+ }
1547
+ const DROP = /* @__PURE__ */ Symbol("drop");
1548
+ function purifyValue(value, strict, onDrop, path, depth) {
1549
+ if (typeof value === "string") {
1550
+ if (isValueSafe(value, strict)) return value;
1551
+ onDrop?.(path, "unsafe-value");
1552
+ return DROP;
1553
+ }
1554
+ if (value === null || typeof value !== "object") {
1555
+ return value;
1556
+ }
1557
+ if (depth >= MAX_SZ_DEPTH) throw new SzDepthError();
1558
+ if (Array.isArray(value)) {
1559
+ const out = [];
1560
+ for (let i = 0; i < value.length; i++) {
1561
+ const cleaned = purifyValue(
1562
+ value[i],
1563
+ strict,
1564
+ onDrop,
1565
+ `${path}[${i}]`,
1566
+ depth + 1
1567
+ );
1568
+ if (cleaned !== DROP) out.push(cleaned);
1569
+ }
1570
+ return out;
1571
+ }
1572
+ return sanitizeObject(value, strict, onDrop, path, depth + 1, false);
1573
+ }
1574
+ function sanitizeObject(input, strict, onDrop, path, depth, applyKeyAllowlist) {
1575
+ if (depth > MAX_SZ_DEPTH) throw new SzDepthError();
1576
+ const out = {};
1577
+ for (const [key, value] of Object.entries(input)) {
1578
+ const keyPath = path ? `${path}.${key}` : key;
1579
+ if (isForbiddenSzKey(key)) {
1580
+ onDrop?.(keyPath, "forbidden-key");
1581
+ continue;
1582
+ }
1583
+ if (applyKeyAllowlist && !SZ_KEY_CATEGORY.has(key)) {
1584
+ if (isVariantKey(key) && value !== null && typeof value === "object" && !Array.isArray(value)) {
1585
+ const cleaned2 = sanitizeObject(
1586
+ value,
1587
+ strict,
1588
+ onDrop,
1589
+ keyPath,
1590
+ depth + 1,
1591
+ true
1592
+ );
1593
+ if (Object.keys(cleaned2).length > 0) out[key] = cleaned2;
1594
+ continue;
1595
+ }
1596
+ onDrop?.(keyPath, "unknown-key");
1597
+ continue;
1598
+ }
1599
+ const cleaned = purifyValue(value, strict, onDrop, keyPath, depth);
1600
+ if (cleaned !== DROP) out[key] = cleaned;
1601
+ }
1602
+ return out;
1603
+ }
1604
+ function purifySz(sz, options = {}) {
1605
+ if (!sz || typeof sz !== "object" || Array.isArray(sz)) return {};
1606
+ const strict = options.strict ?? true;
1607
+ return sanitizeObject(sz, strict, options.onDrop, "", 0, true);
1608
+ }
1609
+
1113
1610
  function dynamic(szProps) {
1114
1611
  const { className } = transform(szProps);
1115
1612
  if (!className) {
@@ -1146,4 +1643,4 @@ function cleanup() {
1146
1643
  resetManifest();
1147
1644
  }
1148
1645
 
1149
- export { cleanup as a, preloadManifest as b, cleanup$1 as c, dynamic as d, preloadManifest$1 as p, resetManifest as r, setManifestUrl as s };
1646
+ export { cleanup as a, preloadManifest as b, cleanup$1 as c, dynamic as d, purifySz as e, preloadManifest$1 as p, resetManifest as r, setManifestUrl as s };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@csszyx/dynamic",
3
- "version": "0.9.10",
3
+ "version": "0.10.1",
4
4
  "description": "Runtime CSS injection engine for @csszyx — injects only CSS not already in the pre-built stylesheet",
5
5
  "type": "module",
6
6
  "repository": {
@@ -38,10 +38,10 @@
38
38
  "node": ">=22.12.0"
39
39
  },
40
40
  "dependencies": {
41
- "@csszyx/compiler": "0.9.10"
41
+ "@csszyx/compiler": "0.10.1"
42
42
  },
43
43
  "peerDependencies": {
44
- "react": ">=18.0.0"
44
+ "react": ">=17.0.0"
45
45
  },
46
46
  "peerDependenciesMeta": {
47
47
  "react": {