@csszyx/cli 0.9.10 → 0.10.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/dist/bin.mjs CHANGED
@@ -6,7 +6,9 @@ import path__default from 'node:path';
6
6
  import fs from 'fs-extra';
7
7
  import ora from 'ora';
8
8
  import pc from 'picocolors';
9
- import { i as transformHtmlSourceSimple, t as transformSource, h as generateTypes } from './shared/cli.dDkXlxD_.mjs';
9
+ import { parseExpression } from '@babel/parser';
10
+ import { transform } from '@csszyx/compiler';
11
+ import { i as transformHtmlSourceSimple, t as transformSource, h as generateTypes } from './shared/cli.C2lx9IpE.mjs';
10
12
  import { execa } from 'execa';
11
13
  import prompts from 'prompts';
12
14
  import readline from 'node:readline';
@@ -18,7 +20,6 @@ import { Minimatch } from 'minimatch';
18
20
  import 'node:fs/promises';
19
21
  import 'node:url';
20
22
  import 'tailwindcss/resolveConfig.js';
21
- import '@babel/parser';
22
23
  import '@babel/types';
23
24
 
24
25
  const colors = {
@@ -329,6 +330,97 @@ async function doctor(options = {}) {
329
330
  }
330
331
  }
331
332
 
333
+ class ExplainParseError extends Error {
334
+ }
335
+ function literalToValue(node) {
336
+ switch (node.type) {
337
+ case "StringLiteral":
338
+ return node.value;
339
+ case "NumericLiteral":
340
+ return node.value;
341
+ case "BooleanLiteral":
342
+ return node.value;
343
+ case "NullLiteral":
344
+ return null;
345
+ case "UnaryExpression":
346
+ if (node.operator === "-" && node.argument.type === "NumericLiteral") {
347
+ return -node.argument.value;
348
+ }
349
+ throw new ExplainParseError(`unsupported unary expression "${node.operator}"`);
350
+ case "TemplateLiteral":
351
+ if (node.expressions.length === 0 && node.quasis.length === 1) {
352
+ return node.quasis[0]?.value.cooked ?? "";
353
+ }
354
+ throw new ExplainParseError("template literals with interpolation are dynamic");
355
+ case "ArrayExpression":
356
+ return arrayToValue(node);
357
+ case "ObjectExpression":
358
+ return objectToValue(node);
359
+ default:
360
+ throw new ExplainParseError(
361
+ `"${node.type}" is dynamic \u2014 explain only resolves static literals`
362
+ );
363
+ }
364
+ }
365
+ function arrayToValue(node) {
366
+ return node.elements.map((element) => {
367
+ if (element === null) {
368
+ throw new ExplainParseError("array holes are not supported");
369
+ }
370
+ if (element.type === "SpreadElement") {
371
+ throw new ExplainParseError("spreads are dynamic and cannot be explained");
372
+ }
373
+ return literalToValue(element);
374
+ });
375
+ }
376
+ function objectToValue(node) {
377
+ const result = {};
378
+ for (const property of node.properties) {
379
+ if (property.type !== "ObjectProperty") {
380
+ throw new ExplainParseError("object methods/spreads are dynamic");
381
+ }
382
+ if (property.computed) {
383
+ throw new ExplainParseError("computed keys are dynamic");
384
+ }
385
+ const { key } = property;
386
+ const name = key.type === "Identifier" ? key.name : key.type === "StringLiteral" ? key.value : null;
387
+ if (name === null) {
388
+ throw new ExplainParseError(`unsupported key type "${key.type}"`);
389
+ }
390
+ if (property.value.type === "AssignmentPattern" || property.value.type === "RestElement") {
391
+ throw new ExplainParseError("pattern values are not supported");
392
+ }
393
+ result[name] = literalToValue(property.value);
394
+ }
395
+ return result;
396
+ }
397
+ function explainSz(input) {
398
+ let expression;
399
+ try {
400
+ expression = parseExpression(input, { plugins: ["typescript"] });
401
+ } catch {
402
+ throw new ExplainParseError("could not parse the sz argument as a JS expression");
403
+ }
404
+ if (expression.type !== "ObjectExpression") {
405
+ throw new ExplainParseError('sz must be an object literal, e.g. "{ p: 4 }"');
406
+ }
407
+ const object = objectToValue(expression);
408
+ const result = transform(object);
409
+ return typeof result === "string" ? result : result.className;
410
+ }
411
+ function explain(sz) {
412
+ let className;
413
+ try {
414
+ className = explainSz(sz);
415
+ } catch (error) {
416
+ const reason = error instanceof ExplainParseError ? error.message : String(error);
417
+ printError(`Could not explain sz: ${reason}`);
418
+ process.exitCode = 1;
419
+ return;
420
+ }
421
+ console.log(className === "" ? "(no classes)" : className);
422
+ }
423
+
332
424
  const VITE_FRAMEWORKS = /* @__PURE__ */ new Set(["vite-react", "vite-vue", "vite-svelte"]);
333
425
  async function readFileOrNull(filePath) {
334
426
  try {
@@ -470,7 +562,40 @@ async function init(options = {}) {
470
562
  }
471
563
  console.log(" \u2022 Check the docs at https://csszyx.com");
472
564
  }
565
+ async function isInsideWorkspace(cwd) {
566
+ let dir = path__default.dirname(path__default.resolve(cwd));
567
+ const { root } = path__default.parse(dir);
568
+ while (dir !== root) {
569
+ if (await fs.pathExists(path__default.join(dir, "pnpm-workspace.yaml")) || await fs.pathExists(path__default.join(dir, "nx.json")) || await fs.pathExists(path__default.join(dir, "lerna.json"))) {
570
+ return true;
571
+ }
572
+ const pkg = await readFileOrNull(path__default.join(dir, "package.json"));
573
+ if (pkg) {
574
+ try {
575
+ if ("workspaces" in JSON.parse(pkg)) return true;
576
+ } catch {
577
+ }
578
+ }
579
+ dir = path__default.dirname(dir);
580
+ }
581
+ return false;
582
+ }
583
+ function tailwindImportBlock(cssDir, cwd, monorepo) {
584
+ if (!monorepo) return '@import "tailwindcss";\n';
585
+ const rel = path__default.relative(path__default.resolve(cssDir), path__default.resolve(cwd)) || ".";
586
+ const src = rel.split(path__default.sep).join("/");
587
+ return `/* Monorepo: scope Tailwind's content detection to this package. Its
588
+ automatic detection otherwise climbs to the workspace root and scans
589
+ sibling packages + docs (.md/.mdx/.txt are NOT ignored), generating
590
+ phantom or broken url() classes. csszyx auto-injects @source for its
591
+ generated classes; this @source covers your own templates. See
592
+ https://csszyx.com/docs/monorepo-content-scope/ */
593
+ @import "tailwindcss" source(none);
594
+ @source "${src}";
595
+ `;
596
+ }
473
597
  async function setupTailwindCss(cwd, framework) {
598
+ const monorepo = await isInsideWorkspace(cwd);
474
599
  let cssPath;
475
600
  let content = null;
476
601
  for (const candidate of CSS_ENTRY_CANDIDATES) {
@@ -485,14 +610,16 @@ async function setupTailwindCss(cwd, framework) {
485
610
  if (!cssPath || content === null) {
486
611
  cssPath = path__default.join(cwd, "src/index.css");
487
612
  await fs.ensureDir(path__default.dirname(cssPath));
488
- await fs.writeFile(cssPath, '@import "tailwindcss";\n');
613
+ await fs.writeFile(cssPath, tailwindImportBlock(path__default.dirname(cssPath), cwd, monorepo));
489
614
  printInfo(`Created ${path__default.relative(cwd, cssPath)} with Tailwind v4 import`);
490
615
  return;
491
616
  }
492
617
  if (!content.includes('@import "tailwindcss"') && !content.includes("@import 'tailwindcss'")) {
493
- await fs.writeFile(cssPath, `@import "tailwindcss";
494
-
495
- ${content}`);
618
+ await fs.writeFile(
619
+ cssPath,
620
+ `${tailwindImportBlock(path__default.dirname(cssPath), cwd, monorepo)}
621
+ ${content}`
622
+ );
496
623
  printInfo(`Added Tailwind v4 import to ${path__default.relative(cwd, cssPath)}`);
497
624
  }
498
625
  if (NEXTJS_FRAMEWORKS.has(framework)) {
@@ -841,6 +968,7 @@ async function migrate(options = {}) {
841
968
  let totalTransformed = 0;
842
969
  let totalSkipped = 0;
843
970
  let totalSkippedComponent = 0;
971
+ let totalSzKeysNormalized = 0;
844
972
  let totalFiles = 0;
845
973
  const allUnrecognized = [];
846
974
  const allWarnings = [];
@@ -849,7 +977,10 @@ async function migrate(options = {}) {
849
977
  for (const filePath of files) {
850
978
  const source = fs$1.readFileSync(filePath, "utf-8");
851
979
  const isHtml = filePath.endsWith(".html");
852
- const hasRelevantAttr = isHtml ? source.includes("class=") : source.includes("className=");
980
+ if (options.keysOnly && isHtml) {
981
+ continue;
982
+ }
983
+ const hasRelevantAttr = isHtml ? source.includes("class=") : options.keysOnly ? source.includes("sz=") : source.includes("className=") || source.includes("sz=");
853
984
  if (!hasRelevantAttr) {
854
985
  continue;
855
986
  }
@@ -866,13 +997,18 @@ async function migrate(options = {}) {
866
997
  injectRuntime: options.injectRuntime,
867
998
  cdnUrl: options.cdnUrl,
868
999
  localPath: options.localPath
869
- }) : transformSource(processSource, filePath, { injectTodos, customMap });
1000
+ }) : transformSource(processSource, filePath, {
1001
+ injectTodos,
1002
+ customMap,
1003
+ keysOnly: options.keysOnly
1004
+ });
870
1005
  allWarnings.push(...result.warnings);
871
1006
  if (result.changed) {
872
1007
  totalFiles++;
873
1008
  totalTransformed += result.stats.classNamesTransformed;
874
1009
  totalSkipped += result.stats.classNamesSkipped;
875
1010
  totalSkippedComponent += result.stats.classNamesSkippedComponent;
1011
+ totalSzKeysNormalized += result.stats.szKeysNormalized ?? 0;
876
1012
  allUnrecognized.push(...result.stats.classesUnrecognized);
877
1013
  if (result.potentiallyUnusedImports.length > 0) {
878
1014
  const rel2 = path__default.relative(cwd, filePath);
@@ -891,20 +1027,25 @@ async function migrate(options = {}) {
891
1027
  }
892
1028
  }
893
1029
  const rel = path__default.relative(cwd, filePath);
1030
+ const detail = options.keysOnly ? `${result.stats.szKeysNormalized ?? 0} sz key(s) normalized` : `${result.stats.classNamesTransformed} className(s) \u2192 sz`;
894
1031
  if (dryRun) {
895
- printInfo(` ${rel}: ${result.stats.classNamesTransformed} className(s) \u2192 sz`);
896
- log.writeLine(` ${rel}: ${result.stats.classNamesTransformed} className(s) \u2192 sz`);
897
- } else {
898
- log.writeLine(` ${rel}: ${result.stats.classNamesTransformed} className(s) \u2192 sz`);
1032
+ printInfo(` ${rel}: ${detail}`);
899
1033
  }
1034
+ log.writeLine(` ${rel}: ${detail}`);
900
1035
  }
901
1036
  }
902
1037
  s2.succeed("Migration complete");
903
1038
  console.info();
904
1039
  printSuccess(`Files modified: ${totalFiles}`);
905
- printSuccess(`classNames converted: ${totalTransformed}`);
1040
+ if (!options.keysOnly) {
1041
+ printSuccess(`classNames converted: ${totalTransformed}`);
1042
+ }
906
1043
  log.writeLine(`Files modified: ${totalFiles}`);
907
1044
  log.writeLine(`classNames converted: ${totalTransformed}`);
1045
+ if (totalSzKeysNormalized > 0) {
1046
+ printSuccess(`legacy sz keys normalized: ${totalSzKeysNormalized}`);
1047
+ log.writeLine(`legacy sz keys normalized: ${totalSzKeysNormalized}`);
1048
+ }
908
1049
  if (totalSkipped > 0) {
909
1050
  printWarn(`classNames skipped (dynamic): ${totalSkipped}`);
910
1051
  log.writeLine(`classNames skipped (dynamic): ${totalSkipped}`);
@@ -1359,6 +1500,11 @@ cli.command("doctor", "Diagnose mangling issues").option("--verbose", "Show deta
1359
1500
  cwd: options.cwd
1360
1501
  });
1361
1502
  });
1503
+ cli.command("explain <sz>", "Print the Tailwind className an sz object compiles to").action(
1504
+ (sz) => {
1505
+ explain(sz);
1506
+ }
1507
+ );
1362
1508
  cli.command("audit", "Analyze mangling performance").option("--json", "Output as JSON").option("--watch", "Live updates").option("--compare <dir>", "Compare with previous build").option("--cwd <dir>", "Current working directory").action(async (options) => {
1363
1509
  await audit({
1364
1510
  json: options.json,
@@ -1378,7 +1524,10 @@ cli.command("generate-types", "Generate TypeScript declarations from tailwind.co
1378
1524
  cli.command("migrate [dir]", "Convert Tailwind className to sz prop").option("--dry-run", "Show changes without modifying files").option("--ignore <patterns>", "Glob patterns to ignore (comma-separated)").option("--pattern <glob>", "Custom glob pattern for file discovery").option("--cwd <dir>", "Current working directory").option("--braces", "Wrap HTML sz values in outer { } braces (default: bare)").option("--no-fouc", "Skip FOUC-prevention CSS injection into HTML files").option("--inject-runtime <mode>", "Inject runtime script into HTML: local | cdn").option("--cdn-url <url>", "Custom CDN URL for --inject-runtime cdn").option(
1379
1525
  "--local-path <path>",
1380
1526
  "Local script path for --inject-runtime local (default: csszyx-runtime.js)"
1381
- ).option("--audit", "Scan without modifying files and output .csszyx-todo.json").option("--inject-todos", "Inject {/* @sz-todo */} comments above unrecognized classes").option("--resolve-todos <file>", "Path to a JSON file mapping custom classes to sz properties").action(async (dir, options) => {
1527
+ ).option("--audit", "Scan without modifying files and output .csszyx-todo.json").option("--inject-todos", "Inject {/* @sz-todo */} comments above unrecognized classes").option("--resolve-todos <file>", "Path to a JSON file mapping custom classes to sz properties").option(
1528
+ "--keys-only",
1529
+ "Only normalize legacy sz-prop keys to their canonical form; leave className untouched (0.9.10 \u2192 0.10.0 upgrade)"
1530
+ ).action(async (dir, options) => {
1382
1531
  await migrate({
1383
1532
  dryRun: options.dryRun,
1384
1533
  ignore: options.ignore ? options.ignore.split(",") : void 0,
@@ -1391,7 +1540,8 @@ cli.command("migrate [dir]", "Convert Tailwind className to sz prop").option("--
1391
1540
  localPath: options.localPath,
1392
1541
  audit: options.audit,
1393
1542
  injectTodos: options.injectTodos,
1394
- resolveTodos: options.resolveTodos
1543
+ resolveTodos: options.resolveTodos,
1544
+ keysOnly: options.keysOnly
1395
1545
  });
1396
1546
  });
1397
1547
  cli.command(
package/dist/index.d.mts CHANGED
@@ -212,6 +212,8 @@ interface TransformResult {
212
212
  /** className kept on capitalized components (they do not accept sz). */
213
213
  classNamesSkippedComponent: number;
214
214
  classesUnrecognized: string[];
215
+ /** Legacy sz-prop keys rewritten to their single-way canonical (transitional). */
216
+ szKeysNormalized?: number;
215
217
  };
216
218
  /** Imports that may be unused after migration (e.g., clsx, cn). */
217
219
  potentiallyUnusedImports: string[];
@@ -224,6 +226,14 @@ interface TransformOptions {
224
226
  injectTodos?: boolean;
225
227
  /** Map of custom classes to sz objects, used to override unrecognized classes */
226
228
  customMap?: CsszyxTodoMap;
229
+ /**
230
+ * If true, ONLY normalize legacy sz-prop keys to their single-way canonical
231
+ * and leave every `className` attribute untouched. The sz-key-only upgrade
232
+ * path for 0.9.10 → 0.10.0.
233
+ *
234
+ * TRANSITIONAL: part of the same legacy-key normalizer; remove at v1.
235
+ */
236
+ keysOnly?: boolean;
227
237
  }
228
238
  /**
229
239
  * Transform a JSX/TSX source file, replacing className with sz props.
package/dist/index.mjs CHANGED
@@ -1,4 +1,4 @@
1
- export { c as classNameToSzObject, e as extractScreenKeys, a as extractSpacingKeys, f as findConfigFile, b as flattenColors, g as generateAndWriteTypes, d as generateTypeDeclarations, h as generateTypes, t as migrateSource, s as scanTailwindConfig, w as writeDeclarationFile } from './shared/cli.dDkXlxD_.mjs';
1
+ export { c as classNameToSzObject, e as extractScreenKeys, a as extractSpacingKeys, f as findConfigFile, b as flattenColors, g as generateAndWriteTypes, d as generateTypeDeclarations, h as generateTypes, t as migrateSource, s as scanTailwindConfig, w as writeDeclarationFile } from './shared/cli.C2lx9IpE.mjs';
2
2
  import 'node:path';
3
3
  import 'node:fs';
4
4
  import 'node:fs/promises';
@@ -6,3 +6,4 @@ import 'node:url';
6
6
  import 'tailwindcss/resolveConfig.js';
7
7
  import '@babel/parser';
8
8
  import '@babel/types';
9
+ import '@csszyx/compiler';
@@ -5,6 +5,7 @@ import { pathToFileURL } from 'node:url';
5
5
  import resolveConfig from 'tailwindcss/resolveConfig.js';
6
6
  import { parse } from '@babel/parser';
7
7
  import * as t from '@babel/types';
8
+ import { REMOVED_BOOLEAN_SUGAR, SUGGESTION_MAP } from '@csszyx/compiler';
8
9
 
9
10
  const CONFIG_FILES = [
10
11
  "tailwind.config.ts",
@@ -147,12 +148,6 @@ const PROPERTY_MAPPINGS = [
147
148
  valueType: "spacing",
148
149
  description: "Gap between flex/grid items"
149
150
  },
150
- {
151
- prop: "grid",
152
- prefix: "grid",
153
- valueType: "grid",
154
- description: "Grid shorthand"
155
- },
156
151
  {
157
152
  prop: "gridCols",
158
153
  prefix: "grid-cols",
@@ -324,7 +319,7 @@ const PROPERTY_MAPPINGS = [
324
319
  description: "Background color"
325
320
  },
326
321
  {
327
- prop: "text",
322
+ prop: "color",
328
323
  prefix: "text",
329
324
  valueType: "colors",
330
325
  stateful: true,
@@ -358,7 +353,7 @@ const PROPERTY_MAPPINGS = [
358
353
  },
359
354
  // Typography
360
355
  {
361
- prop: "font",
356
+ prop: "weight",
362
357
  prefix: "font",
363
358
  valueType: "fontWeight",
364
359
  description: "Font weight"
@@ -370,7 +365,7 @@ const PROPERTY_MAPPINGS = [
370
365
  description: "Font family"
371
366
  },
372
367
  {
373
- prop: "fontSize",
368
+ prop: "text",
374
369
  prefix: "text",
375
370
  valueType: "fontSize",
376
371
  description: "Font size"
@@ -400,12 +395,6 @@ const PROPERTY_MAPPINGS = [
400
395
  valueType: "borderRadius",
401
396
  description: "Border radius"
402
397
  },
403
- {
404
- prop: "borderW",
405
- prefix: "border",
406
- valueType: "borderWidth",
407
- description: "Border width"
408
- },
409
398
  // Effects
410
399
  {
411
400
  prop: "shadow",
@@ -581,11 +570,14 @@ const STATIC_VALUE_TYPES = {
581
570
  transition: ["none", "all", "colors", "opacity", "shadow", "transform"],
582
571
  ease: ["linear", "in", "out", "in-out"]
583
572
  };
573
+ function escapeTsString(value) {
574
+ return value.replace(/\\/g, "\\\\").replace(/'/g, "\\'").replace(/\r/g, "\\r").replace(/\n/g, "\\n");
575
+ }
584
576
  function generateUnionType(values) {
585
577
  if (values.length === 0) {
586
578
  return "string";
587
579
  }
588
- return values.map((v) => `'${v}'`).join(" | ");
580
+ return values.map((v) => `'${escapeTsString(v)}'`).join(" | ");
589
581
  }
590
582
  function generateTypeDeclarations(theme, options = {}) {
591
583
  const { includeComments = true } = options;
@@ -989,6 +981,7 @@ const REVERSE_PROPERTY_MAP = {
989
981
  bg: "bg",
990
982
  "bg-clip": "bgClip",
991
983
  "bg-origin": "bgOrigin",
984
+ "bg-size": "bgSize",
992
985
  // Border Radius
993
986
  rounded: "rounded",
994
987
  "rounded-t": "roundedT",
@@ -1048,6 +1041,11 @@ const REVERSE_PROPERTY_MAP = {
1048
1041
  // Space between
1049
1042
  "space-x": "spaceX",
1050
1043
  "space-y": "spaceY",
1044
+ // Logical margin / padding (block-start / block-end)
1045
+ mbs: "mbs",
1046
+ mbe: "mbe",
1047
+ pbs: "pbs",
1048
+ pbe: "pbe",
1051
1049
  // Sizing
1052
1050
  w: "w",
1053
1051
  "min-w": "minW",
@@ -1056,6 +1054,15 @@ const REVERSE_PROPERTY_MAP = {
1056
1054
  "min-h": "minH",
1057
1055
  "max-h": "maxH",
1058
1056
  size: "size",
1057
+ // Logical sizing (block-size / inline-size). The bare `block`/`inline` classes
1058
+ // are display values (BOOLEAN_VALUE_MAP wins via exact match first); only the
1059
+ // `block-*` / `inline-*` value forms route here.
1060
+ block: "blockSize",
1061
+ inline: "inlineSize",
1062
+ "min-block": "minBlockSize",
1063
+ "max-block": "maxBlockSize",
1064
+ "min-inline": "minInlineSize",
1065
+ "max-inline": "maxInlineSize",
1059
1066
  // Layout
1060
1067
  aspect: "aspect",
1061
1068
  columns: "columns",
@@ -1079,6 +1086,11 @@ const REVERSE_PROPERTY_MAP = {
1079
1086
  inset: "inset",
1080
1087
  "inset-x": "insetX",
1081
1088
  "inset-y": "insetY",
1089
+ // Logical inset sides (inset-s/e + block-start/block-end)
1090
+ "inset-s": "insetS",
1091
+ "inset-e": "insetE",
1092
+ "inset-bs": "insetBs",
1093
+ "inset-be": "insetBe",
1082
1094
  top: "top",
1083
1095
  right: "right",
1084
1096
  bottom: "bottom",
@@ -1089,7 +1101,7 @@ const REVERSE_PROPERTY_MAP = {
1089
1101
  // Typography (ambiguous — text-*, font-* disambiguated)
1090
1102
  text: "color",
1091
1103
  // default for text- prefix
1092
- font: "fontWeight",
1104
+ font: "weight",
1093
1105
  // default for font- prefix
1094
1106
  decoration: "decoration",
1095
1107
  // ambiguous
@@ -1146,6 +1158,8 @@ const REVERSE_PROPERTY_MAP = {
1146
1158
  "mix-blend": "mixBlend",
1147
1159
  "bg-blend": "bgBlend",
1148
1160
  // Filters
1161
+ filter: "filter",
1162
+ "backdrop-filter": "backdropFilter",
1149
1163
  blur: "blur",
1150
1164
  brightness: "brightness",
1151
1165
  contrast: "contrast",
@@ -1168,9 +1182,12 @@ const REVERSE_PROPERTY_MAP = {
1168
1182
  scale: "scale",
1169
1183
  "scale-x": "scaleX",
1170
1184
  "scale-y": "scaleY",
1185
+ "scale-z": "scaleZ",
1171
1186
  rotate: "rotate",
1187
+ translate: "translate",
1172
1188
  "translate-x": "translateX",
1173
1189
  "translate-y": "translateY",
1190
+ "translate-z": "translateZ",
1174
1191
  "skew-x": "skewX",
1175
1192
  "skew-y": "skewY",
1176
1193
  origin: "origin",
@@ -1184,6 +1201,11 @@ const REVERSE_PROPERTY_MAP = {
1184
1201
  cursor: "cursor",
1185
1202
  caret: "caret",
1186
1203
  "pointer-events": "pointerEvents",
1204
+ scheme: "scheme",
1205
+ tab: "tabSize",
1206
+ zoom: "zoom",
1207
+ scrollbar: "scrollbar",
1208
+ "scrollbar-gutter": "scrollbarGutter",
1187
1209
  resize: "resize",
1188
1210
  scroll: "scroll",
1189
1211
  "scroll-m": "scrollM",
@@ -1221,6 +1243,8 @@ const REVERSE_PROPERTY_MAP = {
1221
1243
  // Line clamp
1222
1244
  "line-clamp": "lineClamp",
1223
1245
  wrap: "wrap",
1246
+ // Typography plugin (bare `prose` is boolean; `prose-gray`/`prose-lg` carry a value)
1247
+ prose: "prose",
1224
1248
  // Text shadow
1225
1249
  "text-shadow": "textShadow",
1226
1250
  // Gradient stops
@@ -1237,47 +1261,13 @@ const REVERSE_PROPERTY_MAP = {
1237
1261
  backface: "backface"
1238
1262
  };
1239
1263
  const REVERSE_BOOLEAN_MAP = {
1240
- // Display
1241
- block: "block",
1242
- inline: "inline",
1243
- "inline-block": "inlineBlock",
1244
- flex: "flex",
1245
- "inline-flex": "inlineFlex",
1246
- grid: "grid",
1247
- "inline-grid": "inlineGrid",
1248
- hidden: "hidden",
1249
- contents: "contents",
1250
- table: "table",
1251
- "table-row": "tableRow",
1252
- "table-cell": "tableCell",
1253
- "flow-root": "flowRoot",
1254
- "list-item": "listItem",
1255
- // Position
1256
- static: "static",
1257
- fixed: "fixed",
1258
- absolute: "absolute",
1259
- relative: "relative",
1260
- sticky: "sticky",
1261
- // Visibility
1262
- visible: "visible",
1263
- invisible: "invisible",
1264
- collapse: "collapse",
1265
- // Typography
1264
+ // Typography (composite)
1266
1265
  truncate: "truncate",
1267
- uppercase: "uppercase",
1268
- lowercase: "lowercase",
1269
- capitalize: "capitalize",
1270
- "normal-case": "normalCase",
1271
- underline: "underline",
1272
- overline: "overline",
1273
- "line-through": "lineThrough",
1274
- "no-underline": "noUnderline",
1275
- italic: "italic",
1276
- "not-italic": "notItalic",
1277
- antialiased: "antialiased",
1278
- "subpixel-antialiased": "subpixelAntialiased",
1279
- // Flexbox
1280
- // flexWrap is string-based, not boolean — removed from boolean map
1266
+ // Flexbox (default-or-value: bare `grow`/`shrink` mean grow/shrink: true,
1267
+ // while grow-0/shrink-0 carry a value via REVERSE_PROPERTY_MAP).
1268
+ // flexWrap is string-based, not boolean — kept out of this map.
1269
+ grow: "grow",
1270
+ shrink: "shrink",
1281
1271
  // Filters (defaults)
1282
1272
  blur: "blur",
1283
1273
  grayscale: "grayscale",
@@ -1290,6 +1280,10 @@ const REVERSE_BOOLEAN_MAP = {
1290
1280
  // Misc
1291
1281
  container: "container",
1292
1282
  prose: "prose",
1283
+ // Bare `resize` (resize: both) and bare `shadow` (default elevation) are
1284
+ // default-or-value toggles, like ring/outline below.
1285
+ resize: "resize",
1286
+ shadow: "shadow",
1293
1287
  "sr-only": "srOnly",
1294
1288
  "not-sr-only": "notSrOnly",
1295
1289
  isolate: "isolate",
@@ -1310,10 +1304,8 @@ const REVERSE_BOOLEAN_MAP = {
1310
1304
  "inset-ring": "insetRing",
1311
1305
  outline: "outline",
1312
1306
  rounded: "rounded",
1313
- // Transforms
1314
- "scale-3d": "scale",
1315
- "translate-3d": "translate",
1316
- // transform-gpu/cpu/none use BOOLEAN_VALUE_MAP → { transform: 'gpu'/'cpu'/'none' }
1307
+ // Transforms — scale-3d/translate-3d carry the literal '3d' value via
1308
+ // BOOLEAN_VALUE_MAP, and transform-gpu/cpu/none → { transform: 'gpu'/'cpu'/'none' }.
1317
1309
  // Font numeric
1318
1310
  "normal-nums": "fontVariant",
1319
1311
  "lining-nums": "fontVariant",
@@ -1376,10 +1368,94 @@ const BOOLEAN_VALUE_MAP = {
1376
1368
  // Appearance
1377
1369
  "appearance-none": { prop: "appearance", value: "none" },
1378
1370
  "appearance-auto": { prop: "appearance", value: "auto" },
1371
+ // Isolation (bare `isolate` is in REVERSE_BOOLEAN_MAP; `isolation-auto` carries a value)
1372
+ "isolation-auto": { prop: "isolation", value: "auto", cssProperty: "isolation" },
1373
+ // Field sizing
1374
+ "field-sizing-content": { prop: "fieldSizing", value: "content", cssProperty: "field-sizing" },
1375
+ "field-sizing-fixed": { prop: "fieldSizing", value: "fixed", cssProperty: "field-sizing" },
1379
1376
  // Transform
1380
1377
  "transform-none": { prop: "transform", value: "none" },
1381
1378
  "transform-gpu": { prop: "transform", value: "gpu" },
1382
- "transform-cpu": { prop: "transform", value: "cpu" }
1379
+ "transform-cpu": { prop: "transform", value: "cpu" },
1380
+ // transform-style (3d / flat) — single CSS property, so fail-closed on conflict.
1381
+ "transform-3d": { prop: "transformStyle", value: "3d", cssProperty: "transform-style" },
1382
+ "transform-flat": { prop: "transformStyle", value: "flat", cssProperty: "transform-style" },
1383
+ // 3D scale/translate keyword shorthands carry the literal '3d' value.
1384
+ "scale-3d": { prop: "scale", value: "3d" },
1385
+ "translate-3d": { prop: "translate", value: "3d" },
1386
+ // Single-property utilities — migrated to their canonical { key: value }
1387
+ // form. The boolean-sugar aliases (flex/absolute/italic/...) were removed,
1388
+ // so these never emit `{ flex: true }`; one key per CSS property.
1389
+ // display
1390
+ block: { prop: "display", value: "block", cssProperty: "display" },
1391
+ inline: { prop: "display", value: "inline", cssProperty: "display" },
1392
+ "inline-block": { prop: "display", value: "inline-block", cssProperty: "display" },
1393
+ flex: { prop: "display", value: "flex", cssProperty: "display" },
1394
+ "inline-flex": { prop: "display", value: "inline-flex", cssProperty: "display" },
1395
+ grid: { prop: "display", value: "grid", cssProperty: "display" },
1396
+ "inline-grid": { prop: "display", value: "inline-grid", cssProperty: "display" },
1397
+ hidden: { prop: "display", value: "none", cssProperty: "display" },
1398
+ contents: { prop: "display", value: "contents", cssProperty: "display" },
1399
+ table: { prop: "display", value: "table", cssProperty: "display" },
1400
+ "inline-table": { prop: "display", value: "inline-table", cssProperty: "display" },
1401
+ "table-row": { prop: "display", value: "table-row", cssProperty: "display" },
1402
+ "table-row-group": { prop: "display", value: "table-row-group", cssProperty: "display" },
1403
+ "table-cell": { prop: "display", value: "table-cell", cssProperty: "display" },
1404
+ "table-caption": { prop: "display", value: "table-caption", cssProperty: "display" },
1405
+ "table-column": { prop: "display", value: "table-column", cssProperty: "display" },
1406
+ "table-column-group": {
1407
+ prop: "display",
1408
+ value: "table-column-group",
1409
+ cssProperty: "display"
1410
+ },
1411
+ "table-footer-group": {
1412
+ prop: "display",
1413
+ value: "table-footer-group",
1414
+ cssProperty: "display"
1415
+ },
1416
+ "table-header-group": {
1417
+ prop: "display",
1418
+ value: "table-header-group",
1419
+ cssProperty: "display"
1420
+ },
1421
+ "flow-root": { prop: "display", value: "flow-root", cssProperty: "display" },
1422
+ "list-item": { prop: "display", value: "list-item", cssProperty: "display" },
1423
+ // position
1424
+ static: { prop: "position", value: "static", cssProperty: "position" },
1425
+ fixed: { prop: "position", value: "fixed", cssProperty: "position" },
1426
+ absolute: { prop: "position", value: "absolute", cssProperty: "position" },
1427
+ relative: { prop: "position", value: "relative", cssProperty: "position" },
1428
+ sticky: { prop: "position", value: "sticky", cssProperty: "position" },
1429
+ // visibility
1430
+ visible: { prop: "visibility", value: "visible", cssProperty: "visibility" },
1431
+ invisible: { prop: "visibility", value: "hidden", cssProperty: "visibility" },
1432
+ collapse: { prop: "visibility", value: "collapse", cssProperty: "visibility" },
1433
+ // isolation
1434
+ isolate: { prop: "isolation", value: "isolate", cssProperty: "isolation" },
1435
+ // text-transform
1436
+ uppercase: { prop: "textTransform", value: "uppercase", cssProperty: "text-transform" },
1437
+ lowercase: { prop: "textTransform", value: "lowercase", cssProperty: "text-transform" },
1438
+ capitalize: { prop: "textTransform", value: "capitalize", cssProperty: "text-transform" },
1439
+ "normal-case": { prop: "textTransform", value: "none", cssProperty: "text-transform" },
1440
+ // font-style
1441
+ italic: { prop: "fontStyle", value: "italic", cssProperty: "font-style" },
1442
+ "not-italic": { prop: "fontStyle", value: "normal", cssProperty: "font-style" },
1443
+ // text-decoration-line
1444
+ underline: { prop: "decoration", value: "underline", cssProperty: "text-decoration-line" },
1445
+ overline: { prop: "decoration", value: "overline", cssProperty: "text-decoration-line" },
1446
+ "line-through": {
1447
+ prop: "decoration",
1448
+ value: "line-through",
1449
+ cssProperty: "text-decoration-line"
1450
+ },
1451
+ "no-underline": { prop: "decoration", value: "none", cssProperty: "text-decoration-line" },
1452
+ // font-smoothing
1453
+ antialiased: { prop: "fontSmoothing", value: "grayscale", cssProperty: "font-smoothing" },
1454
+ "subpixel-antialiased": {
1455
+ prop: "fontSmoothing",
1456
+ value: "subpixel",
1457
+ cssProperty: "font-smoothing"
1458
+ }
1383
1459
  };
1384
1460
  const SORTED_PREFIXES = Object.keys(REVERSE_PROPERTY_MAP).sort((a, b) => {
1385
1461
  if (b.length !== a.length) {
@@ -1406,6 +1482,13 @@ const NEGATIVE_ALLOWED = /* @__PURE__ */ new Set([
1406
1482
  "inset-y",
1407
1483
  "start",
1408
1484
  "end",
1485
+ "inset-s",
1486
+ "inset-e",
1487
+ "inset-bs",
1488
+ "inset-be",
1489
+ "mbs",
1490
+ "mbe",
1491
+ "translate",
1409
1492
  "z",
1410
1493
  "order",
1411
1494
  "col",
@@ -1431,7 +1514,10 @@ const NEGATIVE_ALLOWED = /* @__PURE__ */ new Set([
1431
1514
  "scroll-mb",
1432
1515
  "scroll-ml",
1433
1516
  "hue-rotate",
1434
- "backdrop-hue-rotate"
1517
+ "backdrop-hue-rotate",
1518
+ // mask gradient direction carries a leading `-` as part of the value
1519
+ // (e.g. -mask-linear-45 → { mask: '-linear-45' }), not a numeric negation.
1520
+ "mask"
1435
1521
  ]);
1436
1522
  const FRACTION_SUPPORTED = /* @__PURE__ */ new Set([
1437
1523
  "w",
@@ -1451,8 +1537,14 @@ const FRACTION_SUPPORTED = /* @__PURE__ */ new Set([
1451
1537
  "left",
1452
1538
  "start",
1453
1539
  "end",
1540
+ "inset-s",
1541
+ "inset-e",
1542
+ "inset-bs",
1543
+ "inset-be",
1454
1544
  "translate-x",
1455
- "translate-y"
1545
+ "translate-y",
1546
+ "translate",
1547
+ "aspect"
1456
1548
  ]);
1457
1549
  const SPACING_PROPS = /* @__PURE__ */ new Set([
1458
1550
  "p",
@@ -1516,7 +1608,25 @@ const SPACING_PROPS = /* @__PURE__ */ new Set([
1516
1608
  "scroll-pe",
1517
1609
  "border-spacing",
1518
1610
  "translate-x",
1519
- "translate-y"
1611
+ "translate-y",
1612
+ "translate-z",
1613
+ "translate",
1614
+ // Logical sizing accepts the spacing/sizing scale (block-4, inline-full, …)
1615
+ "block",
1616
+ "inline",
1617
+ "min-block",
1618
+ "max-block",
1619
+ "min-inline",
1620
+ "max-inline",
1621
+ // Logical inset sides + block-axis margin/padding
1622
+ "inset-s",
1623
+ "inset-e",
1624
+ "inset-bs",
1625
+ "inset-be",
1626
+ "mbs",
1627
+ "mbe",
1628
+ "pbs",
1629
+ "pbe"
1520
1630
  ]);
1521
1631
  const TEXT_SIZE_KEYWORDS = /* @__PURE__ */ new Set([
1522
1632
  "xs",
@@ -1600,7 +1710,28 @@ const OBJECT_POSITION_KEYWORDS = /* @__PURE__ */ new Set([
1600
1710
  "right-top",
1601
1711
  "right-bottom"
1602
1712
  ]);
1603
- const SHADOW_SIZE_KEYWORDS = /* @__PURE__ */ new Set(["sm", "md", "lg", "xl", "2xl", "inner", "none"]);
1713
+ const SHADOW_SIZE_KEYWORDS = /* @__PURE__ */ new Set([
1714
+ "2xs",
1715
+ "xs",
1716
+ "sm",
1717
+ "md",
1718
+ "lg",
1719
+ "xl",
1720
+ "2xl",
1721
+ "inner",
1722
+ "none"
1723
+ ]);
1724
+ const ALIGN_CONTENT_KEYWORDS = /* @__PURE__ */ new Set([
1725
+ "normal",
1726
+ "center",
1727
+ "start",
1728
+ "end",
1729
+ "between",
1730
+ "around",
1731
+ "evenly",
1732
+ "baseline",
1733
+ "stretch"
1734
+ ]);
1604
1735
  const OUTLINE_STYLE_KEYWORDS = /* @__PURE__ */ new Set(["none", "dashed", "dotted", "double"]);
1605
1736
  const DECORATION_STYLE_KEYWORDS = /* @__PURE__ */ new Set(["solid", "double", "dotted", "dashed", "wavy"]);
1606
1737
  const DECORATION_THICKNESS_KEYWORDS = /* @__PURE__ */ new Set([
@@ -1730,7 +1861,13 @@ function parseClass(cls, options = {}) {
1730
1861
  negative = true;
1731
1862
  negInput = input.slice(1);
1732
1863
  }
1733
- const boolResult = tryBooleanMatch(input, options);
1864
+ if (input === "@container") {
1865
+ return { prop: "@container", value: true };
1866
+ }
1867
+ if (input.startsWith("@container/")) {
1868
+ return { prop: "@container", value: input.slice("@container/".length) };
1869
+ }
1870
+ const boolResult = tryBooleanMatch(input);
1734
1871
  if (boolResult) {
1735
1872
  return applyImportant(boolResult, important);
1736
1873
  }
@@ -1746,7 +1883,10 @@ function parseClass(cls, options = {}) {
1746
1883
  continue;
1747
1884
  }
1748
1885
  if (REVERSE_BOOLEAN_MAP[source]) {
1749
- return applyImportant(booleanClassToParsed(source, options), important);
1886
+ return applyImportant(
1887
+ { prop: REVERSE_BOOLEAN_MAP[source], value: true },
1888
+ important
1889
+ );
1750
1890
  }
1751
1891
  if (prefix === "divide-x" || prefix === "divide-y") {
1752
1892
  return applyImportant({ prop, value: true }, important);
@@ -1785,10 +1925,6 @@ function parseClass(cls, options = {}) {
1785
1925
  }
1786
1926
  }
1787
1927
  }
1788
- const displayResult = tryDisplay(input, options);
1789
- if (displayResult) {
1790
- return applyImportant(displayResult, important);
1791
- }
1792
1928
  if (input.startsWith("[") && input.endsWith("]") && input.includes(":")) {
1793
1929
  const inner = input.slice(1, -1);
1794
1930
  if (inner.startsWith("--")) {
@@ -1808,72 +1944,28 @@ function applyImportant(result, important) {
1808
1944
  if (!important) {
1809
1945
  return result;
1810
1946
  }
1947
+ const base = result.cssProperty ? { cssProperty: result.cssProperty } : {};
1811
1948
  if (typeof result.value === "string") {
1812
- return { prop: result.prop, value: `${result.value}!` };
1949
+ return { ...base, prop: result.prop, value: `${result.value}!` };
1813
1950
  }
1814
1951
  if (typeof result.value === "boolean") {
1815
- return { prop: result.prop, value: "!" };
1952
+ return { ...base, prop: result.prop, value: "!" };
1816
1953
  }
1817
1954
  if (typeof result.value === "number") {
1818
- return { prop: result.prop, value: `${String(result.value)}!` };
1955
+ return { ...base, prop: result.prop, value: `${String(result.value)}!` };
1819
1956
  }
1820
1957
  return result;
1821
1958
  }
1822
- function tryBooleanMatch(cls, options) {
1959
+ function tryBooleanMatch(cls) {
1823
1960
  if (BOOLEAN_VALUE_MAP[cls]) {
1824
- const { prop, value } = BOOLEAN_VALUE_MAP[cls];
1825
- return { prop, value };
1961
+ const { prop, value, cssProperty } = BOOLEAN_VALUE_MAP[cls];
1962
+ return cssProperty ? { prop, value, cssProperty } : { prop, value };
1826
1963
  }
1827
1964
  if (REVERSE_BOOLEAN_MAP[cls]) {
1828
- return booleanClassToParsed(cls, options);
1965
+ return { prop: REVERSE_BOOLEAN_MAP[cls], value: true };
1829
1966
  }
1830
1967
  return null;
1831
1968
  }
1832
- function tryDisplay(cls, options) {
1833
- const displayValues = /* @__PURE__ */ new Set([
1834
- "block",
1835
- "inline",
1836
- "inline-block",
1837
- "flex",
1838
- "inline-flex",
1839
- "grid",
1840
- "inline-grid",
1841
- "hidden",
1842
- "contents",
1843
- "table",
1844
- "table-row",
1845
- "table-cell",
1846
- "flow-root",
1847
- "list-item"
1848
- ]);
1849
- if (displayValues.has(cls)) {
1850
- return REVERSE_BOOLEAN_MAP[cls] ? booleanClassToParsed(cls, options) : null;
1851
- }
1852
- return null;
1853
- }
1854
- function booleanClassToParsed(cls, options) {
1855
- const displayValue = DISPLAY_CLASS_VALUES[cls];
1856
- if (options.display === "canonical" && displayValue) {
1857
- return { prop: "display", value: displayValue, cssProperty: "display" };
1858
- }
1859
- return { prop: REVERSE_BOOLEAN_MAP[cls], value: true };
1860
- }
1861
- const DISPLAY_CLASS_VALUES = {
1862
- block: "block",
1863
- inline: "inline",
1864
- "inline-block": "inline-block",
1865
- flex: "flex",
1866
- "inline-flex": "inline-flex",
1867
- grid: "grid",
1868
- "inline-grid": "inline-grid",
1869
- hidden: "none",
1870
- contents: "contents",
1871
- table: "table",
1872
- "table-row": "table-row",
1873
- "table-cell": "table-cell",
1874
- "flow-root": "flow-root",
1875
- "list-item": "list-item"
1876
- };
1877
1969
  function tryGradient(cls, negative) {
1878
1970
  let input = cls;
1879
1971
  let type = null;
@@ -1999,12 +2091,18 @@ function disambiguate(prefix, value, negative) {
1999
2091
  return disambiguateInsetShadow(value);
2000
2092
  case "stroke":
2001
2093
  return disambiguateStroke(value);
2094
+ case "from":
2095
+ case "via":
2096
+ case "to":
2097
+ return disambiguateGradientStop(prefix, value);
2002
2098
  case "list":
2003
2099
  return disambiguateList(value);
2004
2100
  case "ease":
2005
2101
  return { prop: "ease", value: parseValue("ease", value, negative) };
2006
2102
  case "snap":
2007
2103
  return disambiguateSnap();
2104
+ case "content":
2105
+ return disambiguateContent(value);
2008
2106
  case "flex":
2009
2107
  return disambiguateFlex(value);
2010
2108
  case "table":
@@ -2045,17 +2143,17 @@ function disambiguateText(value) {
2045
2143
  }
2046
2144
  function disambiguateFont(value) {
2047
2145
  if (FONT_WEIGHT_KEYWORDS.has(value)) {
2048
- return { prop: "fontWeight", value };
2146
+ return { prop: "weight", value };
2049
2147
  }
2050
2148
  if (/^\d{3}$/.test(value)) {
2051
- return { prop: "fontWeight", value: parseInt(value, 10) };
2149
+ return { prop: "weight", value: parseInt(value, 10) };
2052
2150
  }
2053
2151
  if (FONT_FAMILY_KEYWORDS.has(value)) {
2054
2152
  return { prop: "fontFamily", value };
2055
2153
  }
2056
2154
  if (value.startsWith("stretch-")) {
2057
2155
  const stretchVal = value.slice("stretch-".length);
2058
- return { prop: "fontStretch", value: stretchVal };
2156
+ return { prop: "fontStretch", value: parseStringValue(stretchVal) };
2059
2157
  }
2060
2158
  if (FONT_STRETCH_KEYWORDS.has(value)) {
2061
2159
  return { prop: "fontStretch", value };
@@ -2087,6 +2185,12 @@ function disambiguateBg(value) {
2087
2185
  if (BG_ATTACHMENT_KEYWORDS.has(value)) {
2088
2186
  return { prop: "bgAttach", value };
2089
2187
  }
2188
+ if (value.startsWith("[") && value.endsWith("]")) {
2189
+ const inner = value.slice(1, -1).replace(/_/g, " ");
2190
+ if (inner.includes(" ") && BG_POSITION_KEYWORDS.has(inner.split(" ")[0])) {
2191
+ return { prop: "bgPos", value: inner };
2192
+ }
2193
+ }
2090
2194
  if (value === "none") {
2091
2195
  return { prop: "bgImg", value: "none" };
2092
2196
  }
@@ -2105,6 +2209,13 @@ function disambiguateShadow(value) {
2105
2209
  if (SHADOW_SIZE_KEYWORDS.has(value)) {
2106
2210
  return { prop: "shadow", value };
2107
2211
  }
2212
+ if (value.startsWith("(") && value.endsWith(")")) {
2213
+ const inner = value.slice(1, -1);
2214
+ if (inner.startsWith("color:")) {
2215
+ return { prop: "shadowColor", value: inner.slice("color:".length) };
2216
+ }
2217
+ return { prop: "shadow", value: inner };
2218
+ }
2108
2219
  return { prop: "shadowColor", value: parseStringValue(value) };
2109
2220
  }
2110
2221
  function disambiguateOutline(value) {
@@ -2125,12 +2236,11 @@ function disambiguateDecoration(value) {
2125
2236
  return { prop: "decorationStyle", value };
2126
2237
  }
2127
2238
  if (DECORATION_THICKNESS_KEYWORDS.has(value)) {
2128
- const num = Number(value);
2129
- if (!Number.isNaN(num)) {
2130
- return { prop: "decorationThickness", value };
2131
- }
2132
2239
  return { prop: "decorationThickness", value };
2133
2240
  }
2241
+ if (isArbitraryDimension(value) || value.startsWith("(") && value.endsWith(")")) {
2242
+ return { prop: "decorationThickness", value: parseStringValue(value) };
2243
+ }
2134
2244
  return { prop: "decorationColor", value: parseStringValue(value) };
2135
2245
  }
2136
2246
  function disambiguateRing(value, negative) {
@@ -2175,8 +2285,18 @@ function disambiguateStroke(value) {
2175
2285
  if (!Number.isNaN(num) && Number.isInteger(num)) {
2176
2286
  return { prop: "strokeWidth", value: num };
2177
2287
  }
2288
+ if (isArbitraryDimension(value)) {
2289
+ return { prop: "strokeWidth", value: parseStringValue(value) };
2290
+ }
2178
2291
  return { prop: "stroke", value: parseStringValue(value) };
2179
2292
  }
2293
+ function disambiguateGradientStop(prefix, value) {
2294
+ const posKey = prefix === "from" ? "fromPos" : prefix === "via" ? "viaPos" : "toPos";
2295
+ if (/^\d+(\.\d+)?%$/.test(value) || /^\d+$/.test(value) || isArbitraryDimension(value)) {
2296
+ return { prop: posKey, value: parseStringValue(value) };
2297
+ }
2298
+ return { prop: prefix, value: parseStringValue(value) };
2299
+ }
2180
2300
  function disambiguateTransition(value) {
2181
2301
  if (TRANSITION_PROPERTY_KEYWORDS.has(value)) {
2182
2302
  return { prop: "transition", value };
@@ -2192,6 +2312,12 @@ function disambiguateList(value) {
2192
2312
  function disambiguateSnap(_value) {
2193
2313
  return null;
2194
2314
  }
2315
+ function disambiguateContent(value) {
2316
+ if (ALIGN_CONTENT_KEYWORDS.has(value)) {
2317
+ return { prop: "alignContent", value };
2318
+ }
2319
+ return { prop: "content", value: parseValue("content", value, false) };
2320
+ }
2195
2321
  function disambiguateFlex(value) {
2196
2322
  const dirValues = /* @__PURE__ */ new Set(["row", "col", "row-reverse", "col-reverse"]);
2197
2323
  if (dirValues.has(value)) {
@@ -2251,6 +2377,8 @@ function isValidSpacingValue(value) {
2251
2377
  "lvh",
2252
2378
  "lvw",
2253
2379
  // Max-width size keywords
2380
+ "3xs",
2381
+ "2xs",
2254
2382
  "xs",
2255
2383
  "sm",
2256
2384
  "md",
@@ -2300,7 +2428,7 @@ function parseValue(prefix, value, negative) {
2300
2428
  return inner;
2301
2429
  }
2302
2430
  if (FRACTION_SUPPORTED.has(prefix) && /^\d+\/\d+$/.test(value)) {
2303
- return value;
2431
+ return negative ? `-${value}` : value;
2304
2432
  }
2305
2433
  if (value === "px") {
2306
2434
  return negative ? "-px" : "px";
@@ -2309,7 +2437,7 @@ function parseValue(prefix, value, negative) {
2309
2437
  return "auto";
2310
2438
  }
2311
2439
  if (value === "full") {
2312
- return "full";
2440
+ return negative ? "-full" : "full";
2313
2441
  }
2314
2442
  if (value === "screen") {
2315
2443
  return "screen";
@@ -2627,7 +2755,7 @@ function parseClassTokenCached(token) {
2627
2755
  }
2628
2756
  function parseClassToken(token) {
2629
2757
  const { variantParts, baseClass } = extractVariants(token);
2630
- const parsed = parseClass(baseClass, { display: "canonical" });
2758
+ const parsed = parseClass(baseClass, { });
2631
2759
  if (!parsed) {
2632
2760
  return null;
2633
2761
  }
@@ -3087,18 +3215,77 @@ function isAstNode(value) {
3087
3215
  function isClassNameJsxAttribute(node) {
3088
3216
  return t.isJSXAttribute(node) && t.isJSXIdentifier(node.name) && node.name.name === "className";
3089
3217
  }
3218
+ function isCleanCanonicalTarget(target) {
3219
+ return /^[a-z][a-z0-9]*$/i.test(target);
3220
+ }
3221
+ function normalizeSzObject(obj, replacements) {
3222
+ let count = 0;
3223
+ for (const prop of obj.properties) {
3224
+ if (!t.isObjectProperty(prop) || prop.computed) {
3225
+ continue;
3226
+ }
3227
+ const key = prop.key;
3228
+ let keyName = null;
3229
+ if (t.isIdentifier(key)) {
3230
+ keyName = key.name;
3231
+ } else if (t.isStringLiteral(key)) {
3232
+ keyName = key.value;
3233
+ }
3234
+ if (keyName === null) {
3235
+ continue;
3236
+ }
3237
+ const sugar = REMOVED_BOOLEAN_SUGAR[keyName];
3238
+ if (sugar && t.isBooleanLiteral(prop.value) && prop.value.value === true && prop.start != null && prop.end != null) {
3239
+ replacements.push({
3240
+ start: prop.start,
3241
+ end: prop.end,
3242
+ text: `${sugar.key}: '${sugar.value}'`
3243
+ });
3244
+ count++;
3245
+ continue;
3246
+ }
3247
+ const suggestion = SUGGESTION_MAP[keyName];
3248
+ if (suggestion && isCleanCanonicalTarget(suggestion) && suggestion !== keyName && key.start != null && key.end != null) {
3249
+ replacements.push({
3250
+ start: key.start,
3251
+ end: key.end,
3252
+ text: t.isStringLiteral(key) ? `'${suggestion}'` : suggestion
3253
+ });
3254
+ count++;
3255
+ }
3256
+ if (t.isObjectExpression(prop.value)) {
3257
+ count += normalizeSzObject(prop.value, replacements);
3258
+ }
3259
+ }
3260
+ return count;
3261
+ }
3090
3262
  function transformSource(source, filePath, options = {}) {
3091
3263
  const warnings = [];
3092
3264
  let classNamesTransformed = 0;
3093
3265
  let classNamesSkipped = 0;
3094
3266
  let classNamesSkippedComponent = 0;
3267
+ let szKeysNormalized = 0;
3095
3268
  const classesUnrecognized = [];
3096
3269
  const replacements = [];
3097
3270
  const clsxImportNames = /* @__PURE__ */ new Set();
3098
3271
  let clsxUsedOutsideClassName = false;
3099
3272
  const clsxCallsitesMigrated = /* @__PURE__ */ new Set();
3100
3273
  let hasCvaImport = false;
3101
- if (source.indexOf("className") === -1 && source.indexOf("cva") === -1) {
3274
+ if (options.keysOnly && source.indexOf("sz=") === -1) {
3275
+ return {
3276
+ code: source,
3277
+ changed: false,
3278
+ warnings: [],
3279
+ stats: {
3280
+ classNamesTransformed: 0,
3281
+ classNamesSkipped: 0,
3282
+ classNamesSkippedComponent: 0,
3283
+ classesUnrecognized: []
3284
+ },
3285
+ potentiallyUnusedImports: []
3286
+ };
3287
+ }
3288
+ if (source.indexOf("className") === -1 && source.indexOf("cva") === -1 && source.indexOf("sz=") === -1) {
3102
3289
  return {
3103
3290
  code: source,
3104
3291
  changed: false,
@@ -3160,6 +3347,16 @@ function transformSource(source, filePath, options = {}) {
3160
3347
  },
3161
3348
  JSXAttribute(node, parent) {
3162
3349
  const attrName = node.name;
3350
+ if (t.isJSXIdentifier(attrName) && attrName.name === "sz") {
3351
+ const szValue = node.value;
3352
+ if (t.isJSXExpressionContainer(szValue) && t.isObjectExpression(szValue.expression)) {
3353
+ szKeysNormalized += normalizeSzObject(szValue.expression, replacements);
3354
+ }
3355
+ return;
3356
+ }
3357
+ if (options.keysOnly) {
3358
+ return;
3359
+ }
3163
3360
  if (!t.isJSXIdentifier(attrName) || attrName.name !== "className") {
3164
3361
  return;
3165
3362
  }
@@ -3324,7 +3521,8 @@ function transformSource(source, filePath, options = {}) {
3324
3521
  classNamesTransformed,
3325
3522
  classNamesSkipped,
3326
3523
  classNamesSkippedComponent,
3327
- classesUnrecognized
3524
+ classesUnrecognized,
3525
+ szKeysNormalized
3328
3526
  },
3329
3527
  potentiallyUnusedImports
3330
3528
  };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@csszyx/cli",
3
- "version": "0.9.10",
3
+ "version": "0.10.0",
4
4
  "description": "Command-line tools for csszyx",
5
5
  "keywords": [
6
6
  "csszyx",
@@ -49,8 +49,9 @@
49
49
  "picocolors": "^1.0.0",
50
50
  "prompts": "^2.4.2",
51
51
  "tailwindcss": "^3.4.1",
52
- "@csszyx/types": "0.9.10",
53
- "@csszyx/unplugin": "0.9.10"
52
+ "@csszyx/compiler": "0.10.0",
53
+ "@csszyx/unplugin": "0.10.0",
54
+ "@csszyx/types": "0.10.0"
54
55
  },
55
56
  "devDependencies": {
56
57
  "@types/fs-extra": "^11.0.4",