synthesisui 0.16.87 → 0.16.88

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.
@@ -527,6 +527,11 @@ function tokenRefFor(name) {
527
527
  return `{spacing.${bare.slice(8)}}`;
528
528
  if (bare.startsWith("shadow-"))
529
529
  return `{shadow.${bare.slice(7)}}`;
530
+ if (bare.startsWith("background-image-gradient-")) {
531
+ return `{gradients.${bare.slice("background-image-gradient-".length)}}`;
532
+ }
533
+ if (bare.startsWith("gradient-"))
534
+ return `{gradients.${bare.slice(9)}}`;
530
535
  if (bare.startsWith("text-"))
531
536
  return `{typography.scale.${bare.slice(5)}.fontSize}`;
532
537
  if (bare.startsWith("font-"))
@@ -271,8 +271,22 @@ export function readUtility(utility, declared) {
271
271
  const bracket = core.indexOf("-[");
272
272
  if (bracket !== -1 && core.endsWith("]")) {
273
273
  const head = core.slice(0, bracket);
274
- const inner = core.slice(bracket + 2, -1);
274
+ let inner = core.slice(bracket + 2, -1);
275
+ /**
276
+ * `bg-[image:var(--gradient-ui)]` - the datatype hint says which property, in
277
+ * Tailwind's own syntax. Two real usages of the signature gradient wear exactly
278
+ * this spelling (dono, 01/08).
279
+ */
280
+ let hinted = null;
281
+ const hint = /^(image|color|length|size|position|url):/.exec(inner);
282
+ if (hint) {
283
+ hinted = hint[1] === "image" ? "backgroundImage" : null;
284
+ inner = inner.slice(hint[0].length);
285
+ }
275
286
  const value = resolveArbitrary(inner, declared);
287
+ if (hinted && value) {
288
+ return { property: hinted, value: value.value, token: value.token };
289
+ }
276
290
  /**
277
291
  * `text-[…]` IS AMBIGUOUS AND THE VALUE DECIDES.
278
292
  *
@@ -299,6 +313,24 @@ export function readUtility(utility, declared) {
299
313
  const rest = core.slice(dash + 1);
300
314
  const colorProp = COLOR_PROPERTY[prefix];
301
315
  if (colorProp) {
316
+ /**
317
+ * `bg-gradient-ui` is the utility Tailwind v4 mints from their
318
+ * `--background-image-gradient-ui`, and the colour path read `gradient-ui` as a
319
+ * colour name and found nothing. The token they declared is the answer - and
320
+ * `bg-gradient-to-r` must NOT land here, because nobody declares `--gradient-to-r`;
321
+ * it falls through to the stop composer below.
322
+ */
323
+ if (prefix === "bg" && rest.startsWith("gradient-")) {
324
+ const name = rest.slice(9);
325
+ if (declared.has(`--background-image-gradient-${name}`) ||
326
+ declared.has(`--gradient-${name}`)) {
327
+ return {
328
+ property: "backgroundImage",
329
+ value: `{gradients.${name}}`,
330
+ token: `--gradient-${name}`,
331
+ };
332
+ }
333
+ }
302
334
  // Their own token first: `--color-ocean-500` for `bg-ocean-500`, which is
303
335
  // exactly how Tailwind v4's `@theme` publishes it.
304
336
  const own = `--color-${rest}`;
@@ -488,6 +520,14 @@ function tokenRefFor(name) {
488
520
  return `{spacing.${bare.slice(8)}}`;
489
521
  if (bare.startsWith("shadow-"))
490
522
  return `{shadow.${bare.slice(7)}}`;
523
+ // `--gradient-ui` and `--background-image-gradient-ui` are one token in two
524
+ // spellings - Tailwind v4's utility namespace wraps the first. Both reach
525
+ // `{gradients.ui}`, which is where the census files them.
526
+ if (bare.startsWith("background-image-gradient-")) {
527
+ return `{gradients.${bare.slice("background-image-gradient-".length)}}`;
528
+ }
529
+ if (bare.startsWith("gradient-"))
530
+ return `{gradients.${bare.slice(9)}}`;
491
531
  if (bare.startsWith("text-")) {
492
532
  return `{typography.scale.${bare.slice(5)}.fontSize}`;
493
533
  }
@@ -561,6 +601,76 @@ const UNITLESS = /^(opacity|zIndex|flex|flexGrow|flexShrink|order|lineHeight|fon
561
601
  * still reads as nothing is a failed DESIGN read, not layout plumbing.
562
602
  */
563
603
  const DESIGN_PREFIX = /^(?:p|px|py|pt|pr|pb|pl|m|mx|my|gap|rounded|text|font|bg|border|shadow|w|h|size|min-w|min-h|max-w|max-h|leading|tracking|opacity|fill|stroke)-\S/;
604
+ /** `to-r` → `to right`: Tailwind's eight directions, spelled as CSS reads them. */
605
+ const GRADIENT_DIRECTION = {
606
+ "to-t": "to top",
607
+ "to-tr": "to top right",
608
+ "to-r": "to right",
609
+ "to-br": "to bottom right",
610
+ "to-b": "to bottom",
611
+ "to-bl": "to bottom left",
612
+ "to-l": "to left",
613
+ "to-tl": "to top left",
614
+ };
615
+ /** A colour stop's value: their token first, Tailwind's table second, a literal last. */
616
+ function stopValue(raw, declared) {
617
+ if (declared.has(`--color-${raw}`))
618
+ return refFor(raw);
619
+ if (TAILWIND_COLOR[raw])
620
+ return TAILWIND_COLOR[raw];
621
+ if (/^#[0-9a-fA-F]{3,8}$/.test(raw) ||
622
+ raw === "transparent" ||
623
+ raw === "white" ||
624
+ raw === "black") {
625
+ return raw;
626
+ }
627
+ return null;
628
+ }
629
+ /**
630
+ * A COMPOSED GRADIENT is one decision spread across four classes.
631
+ *
632
+ * `bg-gradient-to-r from-ocean-500 via-vivid-pink-500 to-purple-400` is nine real
633
+ * elements in one repo, and every piece read as nothing - `from-` is not a property
634
+ * table's prefix. Composed here because only the whole class list can see all the
635
+ * pieces at once; the stops resolve to their own tokens, so the gradient re-themes.
636
+ *
637
+ * `from-bottom-2` taught the guard: `slide-in-from-bottom-2` fragments wear the same
638
+ * prefix, so stops only count when a `bg-gradient-to-*` sits in the SAME class list -
639
+ * and a direction whose stops do not resolve is reported, never half-built.
640
+ */
641
+ function composeGradient(utilities, declared) {
642
+ const dir = utilities.find((u) => GRADIENT_DIRECTION[u.replace(/^bg-gradient-/, "")] &&
643
+ u.startsWith("bg-gradient-"));
644
+ if (!dir)
645
+ return { value: null, consumed: new Set(), broken: [] };
646
+ const direction = GRADIENT_DIRECTION[dir.replace(/^bg-gradient-/, "")];
647
+ const consumed = new Set([dir]);
648
+ const broken = [];
649
+ const stops = [];
650
+ for (const kind of ["from", "via", "to"]) {
651
+ const cls = utilities.find((u) => u.startsWith(`${kind}-`));
652
+ if (!cls)
653
+ continue;
654
+ consumed.add(cls);
655
+ const value = stopValue(cls.slice(kind.length + 1), declared);
656
+ if (value)
657
+ stops.push(value);
658
+ else
659
+ broken.push(cls);
660
+ }
661
+ if (stops.length < 2 || broken.length > 0) {
662
+ return {
663
+ value: null,
664
+ consumed,
665
+ broken: broken.length > 0 ? broken : [dir],
666
+ };
667
+ }
668
+ return {
669
+ value: `linear-gradient(${direction}, ${stops.join(", ")})`,
670
+ consumed,
671
+ broken,
672
+ };
673
+ }
564
674
  export function transcribe(classes, declared) {
565
675
  const out = {
566
676
  base: {},
@@ -572,8 +682,24 @@ export function transcribe(classes, declared) {
572
682
  fromToken: 0,
573
683
  fromLiteral: 0,
574
684
  };
685
+ /**
686
+ * The composed gradient first: it is one decision spread across four classes, and
687
+ * only the whole list sees all the pieces. Base-level only - a per-scheme stop
688
+ * (`dark:from-x`) is a condition this pass does not attempt, and it lands in
689
+ * `unreadable` below rather than half-composing.
690
+ */
691
+ const bareUtilities = classes
692
+ .filter((cls) => parseClass(cls).modifiers.length === 0)
693
+ .map((cls) => parseClass(cls).utility);
694
+ const gradient = composeGradient(bareUtilities, declared);
695
+ if (gradient.value)
696
+ out.base.backgroundImage = gradient.value;
697
+ for (const broke of gradient.broken)
698
+ out.unreadable.push(broke);
575
699
  for (const cls of classes) {
576
700
  const { modifiers, utility } = parseClass(cls);
701
+ if (gradient.consumed.has(utility) && modifiers.length === 0)
702
+ continue;
577
703
  const isDark = modifiers.includes("dark");
578
704
  const rest = modifiers.filter((m) => m !== "dark");
579
705
  // THE SLOT IS DECIDED BEFORE THE VALUE IS READ. Resolving first meant a
@@ -672,6 +672,10 @@ found them wastes a turn and risks contradicting the measurement:
672
672
  - **whether a component forwards the rest of its props**, and which it names
673
673
  - **which components appear inside which** at their call sites, as pairs
674
674
  - **the folder architecture**, and it will ask you which one has priority
675
+ - **their gradients**, as tokens: \`--gradient-ui\` and its Tailwind-namespace twin land on
676
+ \`{gradients.ui}\`, composed \`bg-gradient-to-r from-x to-y\` stops become one gradient in
677
+ their own colour tokens, and a signature gradient is no longer a string trapped in class
678
+ names
675
679
  - **the theme**: how many dark utilities, in how many files, which library switches it, which
676
680
  attribute it is bound to, what \`<html>\` carries, and how many \`prefers-color-scheme\` rules
677
681
  - **the libraries and their versions** - motion, icons, primitives, charts, editors - with the
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "synthesisui",
3
- "version": "0.16.87",
3
+ "version": "0.16.88",
4
4
  "description": "Bring SynthesisUI design systems into any project - tokens, typed components, whole pages and an agent-ready CLAUDE.md manifest.",
5
5
  "type": "module",
6
6
  "bin": {