synthesisui 0.16.213 → 0.16.214

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.
@@ -469,6 +469,12 @@ sxSpacing) {
469
469
  base: { ...fromSx, ...t.base },
470
470
  dark: t.dark,
471
471
  states: t.states,
472
+ /**
473
+ * O TIPO SEMPRE TEVE A CASA E A MONTAGEM NÃO PASSAVA - então a seta do
474
+ * Tooltip real transcrevia `data-[side=top]:top-full` para uma layer e a
475
+ * part chegava sem ela: um posicionador que não posiciona (12/08).
476
+ */
477
+ ...(t.layers?.length ? { layers: t.layers } : {}),
472
478
  };
473
479
  /**
474
480
  * A drop on a PART is still a drop. The root path already says these out loud;
@@ -30,7 +30,7 @@ import { describeForeign, readStyleIslands, } from "../doctor/style-island.js";
30
30
  import { buildLedger, describeLedger, } from "../doctor/style-ledger.js";
31
31
  import { MUI_DEFAULT_SPACING, readStyledComponents, spacingOf, } from "../doctor/style-props.js";
32
32
  import { buildTable } from "../doctor/tokens.js";
33
- import { definitionSpan, readInlineStyle, rootClasses, rootTag, transcribe, } from "../doctor/transcribe.js";
33
+ import { definitionSpan, parseClass, readInlineStyle, rootClasses, rootTag, transcribe, } from "../doctor/transcribe.js";
34
34
  import { transcribeVariants } from "../doctor/variant-read.js";
35
35
  import { frontierKind, packageRoot } from "../frontier-kind.js";
36
36
  import { withLibraryStructure } from "../library-structure.js";
@@ -843,10 +843,25 @@ export async function takeCensus(root, opts) {
843
843
  * animates nothing.
844
844
  */
845
845
  for (const node of sketch) {
846
- for (const raw of (node.classes ?? "").split(/\s+/)) {
847
- const named = /^(?:[\w-]+:)*animate-([a-z][\w-]*)$/.exec(raw);
848
- if (named && named[1] !== "none")
849
- animations.add(named[1]);
846
+ /**
847
+ * O PAR COMPÕE AQUI TAMBÉM - a mesma regra de `composeAnimation`: num elemento
848
+ * que escreve `animate-out fade-out`, o nome que o sistema define é `fade-out`,
849
+ * e `out` sozinho é o motor sem frames. Coletar o motor foi o que pendurou uma
850
+ * animação `in` no censo real (12/08).
851
+ */
852
+ const utilities = (node.classes ?? "")
853
+ .split(/\s+/)
854
+ .filter(Boolean)
855
+ .map((cls) => parseClass(cls).utility);
856
+ for (const utility of utilities) {
857
+ const named = /^animate-([a-z][\w-]*)$/.exec(utility);
858
+ if (!named || named[1] === "none")
859
+ continue;
860
+ const composed = (named[1] === "in" || named[1] === "out") &&
861
+ utilities.includes(`fade-${named[1]}`)
862
+ ? `fade-${named[1]}`
863
+ : named[1];
864
+ animations.add(composed);
850
865
  }
851
866
  }
852
867
  /**
@@ -890,6 +905,12 @@ export async function takeCensus(root, opts) {
890
905
  ...rootPackage(tag, sketch),
891
906
  ...rootBehaviour(tag, sketch),
892
907
  ...(placed.length > 0 ? { layers: placed } : {}),
908
+ /**
909
+ * A MATÉRIA-PRIMA DAS CAMADAS, ainda como classes - ver `raw` em
910
+ * `transcribeVariants`. É o que permite uma tabela de escala nova
911
+ * reinterpretar este censo por deploy, sem `sync` na máquina de ninguém.
912
+ */
913
+ ...(v.raw.length > 0 ? { rawLayers: v.raw } : {}),
893
914
  ...(Object.keys(islandStyle).length > 0 ? { islandStyle } : {}),
894
915
  ...(v.unslotted.length > 0
895
916
  ? { unplaced: [...new Set(v.unslotted)] }
@@ -309,9 +309,16 @@ export function judgeFragments(seen, declared, admitted) {
309
309
  ? { ...f, read: true }
310
310
  : { ...f, read: false, reason: "computed" };
311
311
  const t = transcribe(literal.split(/\s+/).filter(Boolean), declared);
312
+ /**
313
+ * AS QUATRO CASAS DA TRANSCRIÇÃO, não três. O `dark:data-[checked]:bg-…` do
314
+ * RadioCard real transcrevia INTEIRO para uma layer `{state, scheme}` e o juiz
315
+ * só contava base/dark/states - então um fragmento 100% lido ficava na fila de
316
+ * `value-not-read`, e o conserto que a fila pedia já estava entregue (12/08).
317
+ */
312
318
  const read = Object.keys(t.base).length +
313
319
  Object.keys(t.dark).length +
314
- Object.values(t.states ?? {}).reduce((n, b) => n + Object.keys(b).length, 0) >
320
+ Object.values(t.states ?? {}).reduce((n, b) => n + Object.keys(b).length, 0) +
321
+ (t.layers ?? []).reduce((n, l) => n + Object.keys(l.style).length, 0) >
315
322
  0;
316
323
  return read
317
324
  ? { ...f, read: true }
@@ -538,8 +538,42 @@ const STATE_SPELLING = {
538
538
  /** Base UI's enter/exit faces - `data-[starting-style]:opacity-0`. */
539
539
  "starting-style": "startingStyle",
540
540
  "ending-style": "endingStyle",
541
+ /** O par de `open`, que faltava - `data-[closed]:animate-out` no Modal real. */
542
+ closed: "closed",
541
543
  };
542
- const DATA_STATE = /^data-\[(?:state=)?([a-z-]+)\]$/;
544
+ /**
545
+ * `data-[state=open]:`, `data-[checked]:`, `data-[side=top]:` - a MESMA regra do
546
+ * `conditionOf` em `variant-read.ts`, que ganhou atributo=valor em 12/08 e este arquivo
547
+ * não: `data-[side=top]:top-full` tinha o valor na tabela de offset desde sempre e caía
548
+ * em `skipped` porque a CONDIÇÃO não tinha casa aqui. Os dois julgamentos têm que
549
+ * soletrar igual, senão o fragmento e a variante discordam sobre a mesma classe.
550
+ */
551
+ const DATA_STATE = /^data-\[([a-z-]+)(?:=([a-z-]+))?\]$/;
552
+ /** `side-top` → `sideTop`, `starting-style` → `startingStyle`. */
553
+ function camelState(name) {
554
+ return name.replace(/-([a-z0-9])/g, (_, c) => c.toUpperCase());
555
+ }
556
+ /**
557
+ * O nome de estado de um modificador `data-[…]`, ou null quando não é um.
558
+ *
559
+ * `state=open` nomeia pelo VALOR (o atributo é só o veículo); qualquer outro atributo
560
+ * com valor nomeia pelos dois, senão `side=top` e `side=left` colidiriam num estado só.
561
+ * O que não está na tabela mas é legível vira estado por transcrição, nunca descartado:
562
+ * `lost` é para o que não dá para ler, não para o que a gente não previu.
563
+ */
564
+ function dataState(modifier) {
565
+ const data = DATA_STATE.exec(modifier);
566
+ if (!data)
567
+ return null;
568
+ const name = data[1] === "state"
569
+ ? (data[2] ?? "")
570
+ : data[2]
571
+ ? camelState(`${data[1]}-${data[2]}`)
572
+ : data[1];
573
+ if (!name)
574
+ return null;
575
+ return STATE_SPELLING[name] ?? STATE_SPELLING[data[1]] ?? camelState(name);
576
+ }
543
577
  /**
544
578
  * Split a class into its modifiers and the utility itself.
545
579
  *
@@ -1478,6 +1512,39 @@ function composeGradient(utilities, declared) {
1478
1512
  broken,
1479
1513
  };
1480
1514
  }
1515
+ /**
1516
+ * UMA ENTRADA OU SAÍDA ANIMADA É DUAS UTILITIES QUE SÓ SIGNIFICAM JUNTAS - o mesmo
1517
+ * caso do gradiente, uma família ao lado.
1518
+ *
1519
+ * `data-[closed]:animate-out data-[closed]:fade-out` é como o tailwindcss-animate
1520
+ * escreve "sai desvanecendo": `animate-out` é o motor e não nomeia frame nenhum, e
1521
+ * `fade-out` nomeia os frames e não anima nada sozinho. Lidas isoladas, as duas
1522
+ * respostas eram erradas - `animate-out` virava `var(--animate-out)` apontando para um
1523
+ * keyframe que nenhum tema define, e `fade-out` resolvia para nada e sumia sem rastro.
1524
+ * O Modal do repo real fecha exatamente assim, e o `animate-in fade-in` do mesmo
1525
+ * arquivo já tinha posto uma animação `in` pendurada no censo (12/08).
1526
+ *
1527
+ * Composto por grupo de modificador porque é isso que as duas metades têm em comum -
1528
+ * a mesma regra do gradiente. Só a família `fade` entra: é a medida do repo real, e
1529
+ * `zoom-*`/`slide-in-*` ficam nomeados para quando um censo os trouxer.
1530
+ */
1531
+ function composeAnimation(utilities) {
1532
+ const engine = utilities.find((u) => /^animate-(?:in|out)$/.test(u));
1533
+ if (!engine)
1534
+ return { value: null, consumed: new Set() };
1535
+ const direction = engine.slice("animate-".length);
1536
+ const effect = utilities.find((u) => u === `fade-${direction}`);
1537
+ if (!effect)
1538
+ return { value: null, consumed: new Set() };
1539
+ return {
1540
+ value: {
1541
+ property: "animation",
1542
+ value: `var(--animate-${effect})`,
1543
+ keyframe: effect,
1544
+ },
1545
+ consumed: new Set([engine, effect]),
1546
+ };
1547
+ }
1481
1548
  /**
1482
1549
  * WHICH HALF OF THE TRANSCRIPTION A MODIFIER LIST BELONGS TO.
1483
1550
  *
@@ -1517,10 +1584,7 @@ function slotFor(out, modifiers) {
1517
1584
  /** `group-hover:`, `group-data-[checked]:` - the PARENT's state. */
1518
1585
  const group = /^group-(.+)$/.exec(mod);
1519
1586
  const bare = group ? group[1] : mod;
1520
- const raw = STATE_SPELLING[bare] != null
1521
- ? bare
1522
- : (DATA_STATE.exec(bare)?.[1] ?? null);
1523
- const state = raw ? (STATE_SPELLING[raw] ?? null) : null;
1587
+ const state = STATE_SPELLING[bare] ?? dataState(bare);
1524
1588
  if (state) {
1525
1589
  if (group)
1526
1590
  when.within = state;
@@ -1611,6 +1675,33 @@ export function transcribe(classes, declared) {
1611
1675
  out.unreadable.push(at);
1612
1676
  }
1613
1677
  }
1678
+ /** A MESMA COMPOSIÇÃO POR GRUPO, para a família que só significa em par. */
1679
+ for (const group of groups.values()) {
1680
+ const animation = composeAnimation(group.utilities);
1681
+ if (!animation.value)
1682
+ continue;
1683
+ const prefix = group.modifiers.length
1684
+ ? `${group.modifiers.join(":")}:`
1685
+ : "";
1686
+ for (const utility of animation.consumed)
1687
+ consumedByGroup.add(`${prefix}${utility}`);
1688
+ const { target, unslotted } = slotFor(out, group.modifiers);
1689
+ if (unslotted || !target) {
1690
+ for (const utility of animation.consumed)
1691
+ out.skipped.push(`${prefix}${utility}`);
1692
+ continue;
1693
+ }
1694
+ const decl = animation.value;
1695
+ if (decl.keyframe) {
1696
+ out.animations ??= [];
1697
+ if (!out.animations.includes(decl.keyframe))
1698
+ out.animations.push(decl.keyframe);
1699
+ }
1700
+ if (target[decl.property] == null) {
1701
+ target[decl.property] = decl.value;
1702
+ out.fromLiteral += 1;
1703
+ }
1704
+ }
1614
1705
  for (const cls of classes) {
1615
1706
  const { modifiers, utility } = parseClass(cls);
1616
1707
  if (consumedByGroup.has(cls))
@@ -39,6 +39,10 @@ import { parseClass, transcribe } from "./transcribe.js";
39
39
  * all before. `peer-*` is the sibling version of the same idea and reads the same way
40
40
  * from a recipe's point of view.
41
41
  */
42
+ /** `side-top` → `sideTop`, `starting-style` → `startingStyle`. */
43
+ function camel(name) {
44
+ return name.replace(/-([a-z0-9])/g, (_, c) => c.toUpperCase());
45
+ }
42
46
  const MODIFIER_STATE = {
43
47
  hover: "hover",
44
48
  focus: "focus",
@@ -51,6 +55,8 @@ const MODIFIER_STATE = {
51
55
  selected: "selected",
52
56
  highlighted: "highlighted",
53
57
  open: "open",
58
+ /** O par de `open`, e a ausência dele matava o `data-[closed]:animate-out` do Modal dele. */
59
+ closed: "closed",
54
60
  invalid: "invalid",
55
61
  required: "required",
56
62
  "read-only": "readOnly",
@@ -68,8 +74,19 @@ const MODIFIER_STATE = {
68
74
  };
69
75
  /** `group-hover`, `group-focus`, `peer-checked` - the state of something ELSE. */
70
76
  const RELATIONAL = /^(?:group|peer)-(.+)$/;
71
- /** `data-[state=open]:`, `data-[checked]:` - how @base-ui and Radix spell a state. */
72
- const DATA_MODIFIER = /^data-\[(?:state=)?([a-z-]+)\]$/;
77
+ /**
78
+ * `data-[state=open]:`, `data-[checked]:`, `data-[side=top]:` - como @base-ui e Radix escrevem uma
79
+ * condição.
80
+ *
81
+ * A versão anterior aceitava só `state=` ou um nome pelado, então TODO outro atributo com valor caía
82
+ * em `lost`. Medido no repo do dono em 12/08: `data-[side=top]`, `data-[side=bottom]`,
83
+ * `data-[side=left]` e `data-[side=right]` - as quatro posições do Tooltip - morriam ali, e com elas
84
+ * o estilo que só existe quando o popup abre para aquele lado.
85
+ *
86
+ * O atributo E o valor viram o nome, porque é literalmente o que o DOM dele carrega
87
+ * (`data-side="top"`): transcrever não é inventar.
88
+ */
89
+ const DATA_MODIFIER = /^data-\[([a-z-]+)(?:=([a-z-]+))?\]$/;
73
90
  /** A breakpoint is a bare name we cannot read as a state. Their scale names them, so
74
91
  * the caller decides whether the name exists rather than this guessing a width. */
75
92
  const BREAKPOINT = /^(?:xs|sm|md|lg|xl|2xl|3xl|tablet|desktop|wide|mobile)$/;
@@ -102,9 +119,26 @@ export function conditionOf(modifiers) {
102
119
  }
103
120
  const data = DATA_MODIFIER.exec(raw);
104
121
  if (data) {
105
- const state = MODIFIER_STATE[data[1]];
122
+ /**
123
+ * `state=open` nomeia pelo VALOR (o atributo é só o veículo); qualquer outro atributo nomeia
124
+ * pelos dois, senão `side=top` e `side=left` colidiriam num estado só chamado `side`.
125
+ */
126
+ const nome = data[1] === "state"
127
+ ? (data[2] ?? "")
128
+ : data[2]
129
+ ? camel(`${data[1]}-${data[2]}`)
130
+ : data[1];
131
+ const state = MODIFIER_STATE[nome] ?? MODIFIER_STATE[data[1]];
106
132
  if (state)
107
133
  out.state = state;
134
+ /**
135
+ * E O QUE NÃO ESTÁ NA TABELA MAS É LEGÍVEL NÃO SE PERDE. Um `data-side="top"` do base-ui é uma
136
+ * condição do DOM dele, escrita por extenso - descartá-la porque a nossa tabela não a lista
137
+ * seria a tabela decidindo o que o código dele pode dizer. `lost` fica para o que não dá para
138
+ * ler, não para o que a gente não previu.
139
+ */
140
+ else if (nome)
141
+ out.state = camel(nome);
108
142
  else
109
143
  out.lost = true;
110
144
  continue;
@@ -1028,6 +1062,7 @@ export function transcribeVariants(source, declared) {
1028
1062
  axes,
1029
1063
  defaults,
1030
1064
  layers,
1065
+ raw: allLayers,
1031
1066
  base: transcribe(baseClasses, declared),
1032
1067
  unslotted,
1033
1068
  notes,
@@ -100,7 +100,7 @@ export const CHECKER_SINCE = "0.16.202";
100
100
  *
101
101
  * Um censo medido antes disto carrega as duas leituras erradas.
102
102
  */
103
- export const READER_SINCE = "0.16.213";
103
+ export const READER_SINCE = "0.16.214";
104
104
  /**
105
105
  * O QUE ESTÁ INSTALADO AQUI FICOU PARA TRÁS - e as DUAS condições que fazem isso ser verdade.
106
106
  *
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "synthesisui",
3
- "version": "0.16.213",
3
+ "version": "0.16.214",
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": {