synthesisui 0.16.213 → 0.16.215

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;
@@ -13,7 +13,7 @@ import { describeGate, gateComponent, groupSkips, SCREENS_FOR_SYSTEM, } from "..
13
13
  import { emptyTally, internalSpecifiers, scanComponentsInto, tallyToInventory, } from "../doctor/components-scan.js";
14
14
  import { countShape, describeCoverage, summarizeCoverage, } from "../doctor/coverage.js";
15
15
  import { crosswalk, floorSize, isLibrary, observedRules, useLiveCatalogue, } from "../doctor/crosswalk.js";
16
- import { keyframeOffsets, moduleImports, partialCandidates, readModuleCss, readModuleUsage, sheetImports, transcribeModule, } from "../doctor/css-modules.js";
16
+ import { keyframeOffsets, moduleImports, partialCandidates, readGlobalClasses, readModuleCss, readModuleUsage, sheetImports, transcribeModule, } from "../doctor/css-modules.js";
17
17
  import { dataContract } from "../doctor/data-contract.js";
18
18
  import { reconcile, scanDefinitions, } from "../doctor/definitions-scan.js";
19
19
  import { fragmentsOfSource, fragmentsOfStylesheet, judgeFragments, } from "../doctor/fragments.js";
@@ -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";
@@ -227,21 +227,27 @@ into) {
227
227
  for (const candidate of partialCandidates(spec))
228
228
  imported.add(join(dirname(sheet.file), candidate));
229
229
  let css = "";
230
+ const globals = [];
230
231
  for (const { file, body } of sheets) {
231
232
  css += `\n${body}`;
232
- if (!into || !body)
233
+ if (!body)
233
234
  continue;
234
235
  const partial = basename(file).startsWith("_");
236
+ const kind = /\.module\./.test(file) || (partial && imported.has(file))
237
+ ? "scoped"
238
+ : partial
239
+ ? "dead"
240
+ : "global";
241
+ if (kind === "global")
242
+ globals.push({ file: relative(root, file), body });
243
+ if (!into)
244
+ continue;
235
245
  into.push({
236
246
  file: relative(root, file),
237
- fragments: fragmentsOfStylesheet(relative(root, file), body, /\.module\./.test(file) || (partial && imported.has(file))
238
- ? "scoped"
239
- : partial
240
- ? "dead"
241
- : "global"),
247
+ fragments: fragmentsOfStylesheet(relative(root, file), body, kind),
242
248
  });
243
249
  }
244
- return css;
250
+ return { css, globals };
245
251
  }
246
252
  /**
247
253
  * EVERY distinct design value, commonest first - not just the repeated ones.
@@ -378,7 +384,7 @@ export async function takeCensus(root, opts) {
378
384
  useLiveCatalogue(fetched.ok ? asCatalogueTable(fetched.index) : null);
379
385
  /** Todo arquivo de estilo do escopo, para o ledger - ver o parâmetro `into`. */
380
386
  const styleFiles = [];
381
- const css = await harvestOwnCss(root, styleFiles);
387
+ const { css, globals: globalSheets } = await harvestOwnCss(root, styleFiles);
382
388
  const table = buildTable({ css, source: "yours" });
383
389
  const schemes = parseSchemeBlocks(css);
384
390
  const reports = [];
@@ -407,6 +413,34 @@ export async function takeCensus(root, opts) {
407
413
  if (!declaredValues.has(m[1]))
408
414
  declaredValues.set(m[1], m[2].trim());
409
415
  }
416
+ /**
417
+ * AS REGRAS DE CLASSE DAS FOLHAS GLOBAIS - ver `readGlobalClasses`. Uma regra que um
418
+ * componente admitido veste é a receita dele; `claimed` guarda quais foram vestidas,
419
+ * porque é isso que o juiz do ledger precisa para chamar a classe e a regra de LIDAS.
420
+ */
421
+ const globalClasses = readGlobalClasses(globalSheets, declaredValues);
422
+ const globalClassesClaimed = new Set();
423
+ /** Por arquivo, as classes cuja regra a `<style>` island já entregou - ver o juiz. */
424
+ const islandClassesRead = new Map();
425
+ /** A regra global que uma lista de classes veste, mesclada - e a reivindicação. */
426
+ const globalWear = (classes) => {
427
+ const worn = {
428
+ base: {},
429
+ dark: {},
430
+ states: {},
431
+ };
432
+ for (const cls of classes) {
433
+ const rule = globalClasses.get(cls);
434
+ if (!rule)
435
+ continue;
436
+ globalClassesClaimed.add(cls);
437
+ Object.assign(worn.base, rule.base);
438
+ Object.assign(worn.dark, rule.dark);
439
+ for (const [state, block] of Object.entries(rule.states))
440
+ worn.states[state] = { ...worn.states[state], ...block };
441
+ }
442
+ return worn;
443
+ };
410
444
  const looks = {};
411
445
  /**
412
446
  * The directory shape, gathered while the files are already being walked - so
@@ -622,7 +656,13 @@ export async function takeCensus(root, opts) {
622
656
  * the root tag are per component, scoped by name.
623
657
  */
624
658
  for (const extra of found.slice(1)) {
625
- const et = transcribe(rootClasses(src, extra.name), declaredValues);
659
+ const ecls = rootClasses(src, extra.name);
660
+ const et = transcribe(ecls, declaredValues);
661
+ // A raiz do segundo componente também veste folha global - mesma regra.
662
+ const eglobal = globalWear(ecls);
663
+ et.base = { ...eglobal.base, ...et.base };
664
+ et.dark = { ...eglobal.dark, ...et.dark };
665
+ et.states = { ...eglobal.states, ...et.states };
626
666
  const etag = rootTag(src, extra.name);
627
667
  // ITS OWN MARKUP, sliced at the definition boundary - the whole file's
628
668
  // sketch belonged to the first component and the second read an empty
@@ -641,7 +681,14 @@ export async function takeCensus(root, opts) {
641
681
  };
642
682
  }
643
683
  }
644
- const t = transcribe(rootClasses(src, found[0].name), declaredValues);
684
+ const rootCls = rootClasses(src, found[0].name);
685
+ const t = transcribe(rootCls, declaredValues);
686
+ /**
687
+ * A REGRA DE CLASSE GLOBAL QUE A RAIZ VESTE - `clsx("root", …)` contra
688
+ * `.root { isolation: isolate }` numa folha global. É a camada mais fraca de
689
+ * todas: no cascade dele, qualquer coisa dita no elemento ganha da folha.
690
+ */
691
+ const fromGlobal = globalWear(rootCls);
645
692
  /**
646
693
  * The transcription carries `dark` too, and spreading it put the key back after
647
694
  * the conditional below decided to leave it out. Two writers for one field is
@@ -739,6 +786,7 @@ export async function takeCensus(root, opts) {
739
786
  */
740
787
  const styledHere = readStyledComponents(src).find((c) => c.name === found[0].name);
741
788
  const base = {
789
+ ...fromGlobal.base,
742
790
  ...inlineStyle,
743
791
  ...styledHere?.base,
744
792
  ...fromModule?.base,
@@ -746,12 +794,14 @@ export async function takeCensus(root, opts) {
746
794
  ...t.base,
747
795
  };
748
796
  const states = {
797
+ ...fromGlobal.states,
749
798
  ...styledHere?.states,
750
799
  ...fromModule?.states,
751
800
  ...v.base.states,
752
801
  ...t.states,
753
802
  };
754
803
  const dark = {
804
+ ...fromGlobal.dark,
755
805
  ...styledHere?.dark,
756
806
  ...fromModule?.dark,
757
807
  ...t.dark,
@@ -820,9 +870,37 @@ export async function takeCensus(root, opts) {
820
870
  if (mine.length === 0)
821
871
  continue;
822
872
  islandStyle[index] = Object.assign(islandStyle[index] ?? {}, ...mine.map((rule) => rule.declarations));
873
+ /**
874
+ * A CLASSE FOI LIDA - pela island, e o juiz do ledger precisa saber,
875
+ * senão o `dot` cuja regra o censo carrega segue acusado de valor
876
+ * perdido (era 3 das 6 ocorrências finais da fila, 12/08).
877
+ */
878
+ const claimed = islandClassesRead.get(rel) ?? new Set();
879
+ claimed.add(cls);
880
+ islandClassesRead.set(rel, claimed);
823
881
  }
824
882
  }
825
883
  }
884
+ /**
885
+ * A REGRA DE CLASSE GLOBAL QUE UM NÓ INTERNO VESTE viaja pela MESMA costura do
886
+ * island: por índice do sketch, resolvida em part quando a anatomia nomeia o nó.
887
+ * A raiz (índice 0) já entrou no `base` acima; o island e as utilities do próprio
888
+ * nó ganham dela, porque no cascade dele a folha global é a camada mais fraca.
889
+ */
890
+ for (const [index, node] of sketch.entries()) {
891
+ if (index === 0)
892
+ continue;
893
+ for (const cls of (node.classes ?? "").split(/\s+/)) {
894
+ const rule = cls ? globalClasses.get(cls) : undefined;
895
+ if (!rule)
896
+ continue;
897
+ globalClassesClaimed.add(cls);
898
+ islandStyle[index] = {
899
+ ...rule.base,
900
+ ...(islandStyle[index] ?? {}),
901
+ };
902
+ }
903
+ }
826
904
  if (island.foreign.length > 0) {
827
905
  /**
828
906
  * WHOSE MARKUP IS BEING PATCHED, measured rather than guessed: the opaque
@@ -843,10 +921,25 @@ export async function takeCensus(root, opts) {
843
921
  * animates nothing.
844
922
  */
845
923
  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]);
924
+ /**
925
+ * O PAR COMPÕE AQUI TAMBÉM - a mesma regra de `composeAnimation`: num elemento
926
+ * que escreve `animate-out fade-out`, o nome que o sistema define é `fade-out`,
927
+ * e `out` sozinho é o motor sem frames. Coletar o motor foi o que pendurou uma
928
+ * animação `in` no censo real (12/08).
929
+ */
930
+ const utilities = (node.classes ?? "")
931
+ .split(/\s+/)
932
+ .filter(Boolean)
933
+ .map((cls) => parseClass(cls).utility);
934
+ for (const utility of utilities) {
935
+ const named = /^animate-([a-z][\w-]*)$/.exec(utility);
936
+ if (!named || named[1] === "none")
937
+ continue;
938
+ const composed = (named[1] === "in" || named[1] === "out") &&
939
+ utilities.includes(`fade-${named[1]}`)
940
+ ? `fade-${named[1]}`
941
+ : named[1];
942
+ animations.add(composed);
850
943
  }
851
944
  }
852
945
  /**
@@ -890,6 +983,12 @@ export async function takeCensus(root, opts) {
890
983
  ...rootPackage(tag, sketch),
891
984
  ...rootBehaviour(tag, sketch),
892
985
  ...(placed.length > 0 ? { layers: placed } : {}),
986
+ /**
987
+ * A MATÉRIA-PRIMA DAS CAMADAS, ainda como classes - ver `raw` em
988
+ * `transcribeVariants`. É o que permite uma tabela de escala nova
989
+ * reinterpretar este censo por deploy, sem `sync` na máquina de ninguém.
990
+ */
991
+ ...(v.raw.length > 0 ? { rawLayers: v.raw } : {}),
893
992
  ...(Object.keys(islandStyle).length > 0 ? { islandStyle } : {}),
894
993
  ...(v.unslotted.length > 0
895
994
  ? { unplaced: [...new Set(v.unslotted)] }
@@ -1482,7 +1581,11 @@ export async function takeCensus(root, opts) {
1482
1581
  * Montado aqui porque é o primeiro momento em que as três coisas existem juntas: os fragmentos
1483
1582
  * vistos, os tokens declarados, e quais arquivos de estilo pertencem a componente admitido.
1484
1583
  */
1485
- const ledger = buildLedger(opts?.cli ?? "unknown", judgeFragments([...fragments.flat(), ...styleFiles.flatMap((s) => s.fragments)], declaredValues, admittedStyles));
1584
+ const ledger = buildLedger(opts?.cli ?? "unknown", judgeFragments([...fragments.flat(), ...styleFiles.flatMap((s) => s.fragments)], declaredValues, admittedStyles, {
1585
+ wornGlobal: globalClassesClaimed,
1586
+ islandRead: islandClassesRead,
1587
+ refused: new Set(skips.map((s) => s.file)),
1588
+ }));
1486
1589
  const coverageLines = describeCoverage(coverage, composition);
1487
1590
  if (coverageLines.length > 0) {
1488
1591
  say("");
@@ -683,3 +683,50 @@ function tokenRefFor(name) {
683
683
  return `{typography.families.${bare.slice(5)}}`;
684
684
  return `var(${name})`;
685
685
  }
686
+ /**
687
+ * AS REGRAS DE CLASSE DE UMA FOLHA GLOBAL, prontas para o componente que as veste.
688
+ *
689
+ * Uma folha global vale para a página inteira - por isso ela nunca teve leitor de
690
+ * componente. Mas uma regra de CLASSE que um componente admitido veste literalmente é
691
+ * a receita DELE: o `.root { isolation: isolate }` de um repo real pertence ao
692
+ * RootWrapper que escreve `clsx("root", …)`, e era a última ocorrência de
693
+ * `value-not-read` (12/08). Medido antes de construir: a massa da família é regra de
694
+ * ELEMENTO (106 de 115 declarações no packages/ui), e regra de classe vestida existe
695
+ * UMA - este leitor lê exatamente o que existe, e o resto continua declarado.
696
+ *
697
+ * Reusa o MESMO leitor de regra do module (`readModuleCss`), então estados e a face
698
+ * dark vêm de graça - um segundo parser seria a segunda fonte de verdade que este
699
+ * arquivo recusa em todo lugar. Fica de fora, nomeado: classe que só aparece como
700
+ * descendente (`.a .b` - o contexto é do pai) e seletor composto, que `readModuleCss`
701
+ * já reporta em `unslotted`.
702
+ */
703
+ export function readGlobalClasses(globals, declared) {
704
+ const resolve = (block) => {
705
+ const out = {};
706
+ for (const [prop, value] of Object.entries(block)) {
707
+ out[prop] = value.replace(/var\(\s*(--[a-zA-Z0-9_-]+)\s*(?:,[^)]*)?\)/g, (whole, name) => declared.has(name) ? tokenRefFor(name) : whole);
708
+ }
709
+ return out;
710
+ };
711
+ const out = new Map();
712
+ for (const sheet of globals) {
713
+ const read = readModuleCss(sheet.body);
714
+ const dependent = new Set(Object.values(read.classes).flatMap((c) => c.children));
715
+ for (const [name, cls] of Object.entries(read.classes)) {
716
+ /** O filho de `.a .b` só existe naquele contexto - vesti-lo solto pintaria errado. */
717
+ if (dependent.has(name))
718
+ continue;
719
+ const states = {};
720
+ for (const [state, block] of Object.entries(cls.states))
721
+ states[state] = resolve(block);
722
+ // A última folha ganha, que é como o cascade dele resolve duas regras iguais.
723
+ out.set(name, {
724
+ ...cls,
725
+ base: resolve(cls.base),
726
+ dark: resolve(cls.dark),
727
+ states,
728
+ });
729
+ }
730
+ }
731
+ return out;
732
+ }
@@ -260,8 +260,60 @@ kind) {
260
260
  * fatos diferentes que, somados num número só, fizeram 36% parecer defeito de leitura quando era
261
261
  * o portão funcionando (dono, 04/08).
262
262
  */
263
- export function judgeFragments(seen, declared, admitted) {
263
+ /**
264
+ * O VOCABULÁRIO DE ESTRUTURA - o que a anatomia carrega como forma e direção do nó,
265
+ * nunca como declaração de estilo. É o conjunto que `arrangement()` e a derivação de
266
+ * forma já leem; crescer isto sem crescer aquilo voltaria a ser acusação.
267
+ */
268
+ const STRUCTURE = new Set([
269
+ "flex",
270
+ "inline-flex",
271
+ "grid",
272
+ "inline-grid",
273
+ "flex-row",
274
+ "flex-col",
275
+ "flex-row-reverse",
276
+ "flex-col-reverse",
277
+ "block",
278
+ "inline-block",
279
+ "inline",
280
+ "contents",
281
+ ]);
282
+ export function judgeFragments(seen, declared, admitted,
283
+ /**
284
+ * AS CLASSES DE FOLHA GLOBAL QUE UM COMPONENTE ADMITIDO VESTIU - ver
285
+ * `readGlobalClasses` e a reivindicação em `import.ts`. Vestida, a regra é a
286
+ * receita do componente e as duas metades do fato são LIDAS: a declaração na
287
+ * folha e a classe no elemento.
288
+ */
289
+ /**
290
+ * O QUE FOI LIDO POR OUTRO CAMINHO E QUEM SAIU PELO PORTÃO - as três chegaram
291
+ * juntas em 12/08, medindo os últimos textos da fila de `value-not-read`:
292
+ *
293
+ * wornGlobal classes de folha global que um admitido vestiu (`page-shell`) -
294
+ * a regra está no look; a classe e a regra são LIDAS
295
+ * islandRead por arquivo, as classes cuja regra mora na `<style>` island que o
296
+ * censo JÁ lê (`dot` do TypingIndicator) - mesmo fato, outra folha
297
+ * refused arquivos de componente que o portão recusou de propósito: a classe
298
+ * deles é `component-not-admitted` (by-design), nunca lacuna de valor
299
+ */
300
+ elsewhere) {
301
+ const wornGlobal = elsewhere?.wornGlobal ?? new Set();
302
+ const islandRead = elsewhere?.islandRead ?? new Map();
303
+ const refused = elsewhere?.refused ?? new Set();
264
304
  return seen.map((f) => {
305
+ /**
306
+ * A REGRA DE CLASSE GLOBAL QUE ALGUÉM VESTE FOI LIDA - antes do desvio de
307
+ * `binding`, porque lida é mais forte que ligada. `.root { isolation: isolate }`
308
+ * era a última ocorrência de `value-not-read` do repo real (12/08).
309
+ */
310
+ if (f.shape === "css" && f.reason === "shape-not-read") {
311
+ const cls = /^\.([A-Za-z][\w-]*)\s*\{/.exec(f.text)?.[1];
312
+ if (cls && wornGlobal.has(cls)) {
313
+ const { reason: _clear, ...rest } = f;
314
+ return { ...rest, read: true };
315
+ }
316
+ }
265
317
  /**
266
318
  * UMA DECLARAÇÃO SEM LEITOR CUJO VALOR É UM TOKEN QUE ELES JÁ DECLARAM.
267
319
  *
@@ -309,13 +361,47 @@ export function judgeFragments(seen, declared, admitted) {
309
361
  ? { ...f, read: true }
310
362
  : { ...f, read: false, reason: "computed" };
311
363
  const t = transcribe(literal.split(/\s+/).filter(Boolean), declared);
364
+ /**
365
+ * AS QUATRO CASAS DA TRANSCRIÇÃO, não três. O `dark:data-[checked]:bg-…` do
366
+ * RadioCard real transcrevia INTEIRO para uma layer `{state, scheme}` e o juiz
367
+ * só contava base/dark/states - então um fragmento 100% lido ficava na fila de
368
+ * `value-not-read`, e o conserto que a fila pedia já estava entregue (12/08).
369
+ */
312
370
  const read = Object.keys(t.base).length +
313
371
  Object.keys(t.dark).length +
314
- Object.values(t.states ?? {}).reduce((n, b) => n + Object.keys(b).length, 0) >
372
+ Object.values(t.states ?? {}).reduce((n, b) => n + Object.keys(b).length, 0) +
373
+ (t.layers ?? []).reduce((n, l) => n + Object.keys(l.style).length, 0) >
315
374
  0;
316
- return read
317
- ? { ...f, read: true }
318
- : { ...f, read: false, reason: "value-not-read" };
375
+ if (read)
376
+ return { ...f, read: true };
377
+ /**
378
+ * ZERO DECLARAÇÕES NÃO É SEMPRE VALOR PERDIDO - duas formas de lista que a
379
+ * esteira lê por OUTRO caminho, medidas no repo real em 12/08 (eram os 3
380
+ * últimos textos da fila de `value-not-read`):
381
+ *
382
+ * flex flex-col arranjo viaja como FORMA do nó (Lei 12, `arrangement()`),
383
+ * e o transcribe devolve zero de propósito
384
+ * root a regra está numa folha GLOBAL que um admitido veste -
385
+ * o leitor de classe global a entrega no look
386
+ *
387
+ * Só quando TODO token tem um desses destinos: um token desconhecido no meio
388
+ * mantém o fragmento na fila, que é onde ele conta.
389
+ */
390
+ const tokens = literal.split(/\s+/).filter(Boolean);
391
+ const readElsewhere = tokens.length > 0 &&
392
+ tokens.every((tk) => STRUCTURE.has(tk) ||
393
+ wornGlobal.has(tk) ||
394
+ islandRead.get(f.file)?.has(tk));
395
+ if (readElsewhere)
396
+ return { ...f, read: true };
397
+ /** A classe de um componente recusado é o portão desenhado, não valor perdido. */
398
+ if (refused.has(f.file))
399
+ return {
400
+ ...f,
401
+ read: false,
402
+ reason: "component-not-admitted",
403
+ };
404
+ return { ...f, read: false, reason: "value-not-read" };
319
405
  }
320
406
  case "inline": {
321
407
  const read = Object.keys(readInlineStyle(`style={{${f.text}}}`)).length > 0;
@@ -448,6 +448,17 @@ const PLACE_KEYWORD = {
448
448
  property: "overflow+textOverflow+whiteSpace",
449
449
  value: "overflow:hidden|textOverflow:ellipsis|whiteSpace:nowrap",
450
450
  },
451
+ /**
452
+ * ACESSIBILIDADE E ROLAGEM SÃO DECLARAÇÕES TAMBÉM - dois dos três utilitários que
453
+ * seguravam a fila de `value-not-read` do repo real depois de tudo lido (12/08):
454
+ * `sr-only` no título do Modal e `scrollbar-gutter-stable` no chat. Valores
455
+ * PUBLICADOS do Tailwind, transcritos verbatim - nada inventado.
456
+ */
457
+ "sr-only": {
458
+ property: "position+width+height+padding+margin+overflow+clip+whiteSpace+borderWidth",
459
+ value: "position:absolute|width:1px|height:1px|padding:0|margin:-1px|overflow:hidden|clip:rect(0, 0, 0, 0)|whiteSpace:nowrap|borderWidth:0",
460
+ },
461
+ "scrollbar-gutter-stable": { property: "scrollbarGutter", value: "stable" },
451
462
  };
452
463
  /**
453
464
  * MOVEMENT IS A DECLARATION TOO, and 91 utilities of it were being dropped.
@@ -538,8 +549,42 @@ const STATE_SPELLING = {
538
549
  /** Base UI's enter/exit faces - `data-[starting-style]:opacity-0`. */
539
550
  "starting-style": "startingStyle",
540
551
  "ending-style": "endingStyle",
552
+ /** O par de `open`, que faltava - `data-[closed]:animate-out` no Modal real. */
553
+ closed: "closed",
541
554
  };
542
- const DATA_STATE = /^data-\[(?:state=)?([a-z-]+)\]$/;
555
+ /**
556
+ * `data-[state=open]:`, `data-[checked]:`, `data-[side=top]:` - a MESMA regra do
557
+ * `conditionOf` em `variant-read.ts`, que ganhou atributo=valor em 12/08 e este arquivo
558
+ * não: `data-[side=top]:top-full` tinha o valor na tabela de offset desde sempre e caía
559
+ * em `skipped` porque a CONDIÇÃO não tinha casa aqui. Os dois julgamentos têm que
560
+ * soletrar igual, senão o fragmento e a variante discordam sobre a mesma classe.
561
+ */
562
+ const DATA_STATE = /^data-\[([a-z-]+)(?:=([a-z-]+))?\]$/;
563
+ /** `side-top` → `sideTop`, `starting-style` → `startingStyle`. */
564
+ function camelState(name) {
565
+ return name.replace(/-([a-z0-9])/g, (_, c) => c.toUpperCase());
566
+ }
567
+ /**
568
+ * O nome de estado de um modificador `data-[…]`, ou null quando não é um.
569
+ *
570
+ * `state=open` nomeia pelo VALOR (o atributo é só o veículo); qualquer outro atributo
571
+ * com valor nomeia pelos dois, senão `side=top` e `side=left` colidiriam num estado só.
572
+ * O que não está na tabela mas é legível vira estado por transcrição, nunca descartado:
573
+ * `lost` é para o que não dá para ler, não para o que a gente não previu.
574
+ */
575
+ function dataState(modifier) {
576
+ const data = DATA_STATE.exec(modifier);
577
+ if (!data)
578
+ return null;
579
+ const name = data[1] === "state"
580
+ ? (data[2] ?? "")
581
+ : data[2]
582
+ ? camelState(`${data[1]}-${data[2]}`)
583
+ : data[1];
584
+ if (!name)
585
+ return null;
586
+ return STATE_SPELLING[name] ?? STATE_SPELLING[data[1]] ?? camelState(name);
587
+ }
543
588
  /**
544
589
  * Split a class into its modifiers and the utility itself.
545
590
  *
@@ -681,6 +726,14 @@ function readUtilityCore(utility, declared) {
681
726
  * frames themselves travel beside the reading - see `BUILTIN_KEYFRAMES` - because a
682
727
  * recipe cannot point at a keyframe the system does not define.
683
728
  */
729
+ /**
730
+ * `cursor-help`, `cursor-not-allowed` - o cursor É uma decisão de interação, e o
731
+ * nome do Tailwind é o valor CSS verbatim em toda a escala publicada. O gatilho do
732
+ * MetricCard real diz "isto tem explicação" com ele (12/08).
733
+ */
734
+ const cursor = /^cursor-([a-z-]+)$/.exec(core);
735
+ if (cursor)
736
+ return { property: "cursor", value: cursor[1] };
684
737
  const animate = /^animate-([a-z][\w-]*)$/.exec(core);
685
738
  if (animate && animate[1] !== "none") {
686
739
  return {
@@ -1478,6 +1531,39 @@ function composeGradient(utilities, declared) {
1478
1531
  broken,
1479
1532
  };
1480
1533
  }
1534
+ /**
1535
+ * UMA ENTRADA OU SAÍDA ANIMADA É DUAS UTILITIES QUE SÓ SIGNIFICAM JUNTAS - o mesmo
1536
+ * caso do gradiente, uma família ao lado.
1537
+ *
1538
+ * `data-[closed]:animate-out data-[closed]:fade-out` é como o tailwindcss-animate
1539
+ * escreve "sai desvanecendo": `animate-out` é o motor e não nomeia frame nenhum, e
1540
+ * `fade-out` nomeia os frames e não anima nada sozinho. Lidas isoladas, as duas
1541
+ * respostas eram erradas - `animate-out` virava `var(--animate-out)` apontando para um
1542
+ * keyframe que nenhum tema define, e `fade-out` resolvia para nada e sumia sem rastro.
1543
+ * O Modal do repo real fecha exatamente assim, e o `animate-in fade-in` do mesmo
1544
+ * arquivo já tinha posto uma animação `in` pendurada no censo (12/08).
1545
+ *
1546
+ * Composto por grupo de modificador porque é isso que as duas metades têm em comum -
1547
+ * a mesma regra do gradiente. Só a família `fade` entra: é a medida do repo real, e
1548
+ * `zoom-*`/`slide-in-*` ficam nomeados para quando um censo os trouxer.
1549
+ */
1550
+ function composeAnimation(utilities) {
1551
+ const engine = utilities.find((u) => /^animate-(?:in|out)$/.test(u));
1552
+ if (!engine)
1553
+ return { value: null, consumed: new Set() };
1554
+ const direction = engine.slice("animate-".length);
1555
+ const effect = utilities.find((u) => u === `fade-${direction}`);
1556
+ if (!effect)
1557
+ return { value: null, consumed: new Set() };
1558
+ return {
1559
+ value: {
1560
+ property: "animation",
1561
+ value: `var(--animate-${effect})`,
1562
+ keyframe: effect,
1563
+ },
1564
+ consumed: new Set([engine, effect]),
1565
+ };
1566
+ }
1481
1567
  /**
1482
1568
  * WHICH HALF OF THE TRANSCRIPTION A MODIFIER LIST BELONGS TO.
1483
1569
  *
@@ -1517,10 +1603,7 @@ function slotFor(out, modifiers) {
1517
1603
  /** `group-hover:`, `group-data-[checked]:` - the PARENT's state. */
1518
1604
  const group = /^group-(.+)$/.exec(mod);
1519
1605
  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;
1606
+ const state = STATE_SPELLING[bare] ?? dataState(bare);
1524
1607
  if (state) {
1525
1608
  if (group)
1526
1609
  when.within = state;
@@ -1611,6 +1694,33 @@ export function transcribe(classes, declared) {
1611
1694
  out.unreadable.push(at);
1612
1695
  }
1613
1696
  }
1697
+ /** A MESMA COMPOSIÇÃO POR GRUPO, para a família que só significa em par. */
1698
+ for (const group of groups.values()) {
1699
+ const animation = composeAnimation(group.utilities);
1700
+ if (!animation.value)
1701
+ continue;
1702
+ const prefix = group.modifiers.length
1703
+ ? `${group.modifiers.join(":")}:`
1704
+ : "";
1705
+ for (const utility of animation.consumed)
1706
+ consumedByGroup.add(`${prefix}${utility}`);
1707
+ const { target, unslotted } = slotFor(out, group.modifiers);
1708
+ if (unslotted || !target) {
1709
+ for (const utility of animation.consumed)
1710
+ out.skipped.push(`${prefix}${utility}`);
1711
+ continue;
1712
+ }
1713
+ const decl = animation.value;
1714
+ if (decl.keyframe) {
1715
+ out.animations ??= [];
1716
+ if (!out.animations.includes(decl.keyframe))
1717
+ out.animations.push(decl.keyframe);
1718
+ }
1719
+ if (target[decl.property] == null) {
1720
+ target[decl.property] = decl.value;
1721
+ out.fromLiteral += 1;
1722
+ }
1723
+ }
1614
1724
  for (const cls of classes) {
1615
1725
  const { modifiers, utility } = parseClass(cls);
1616
1726
  if (consumedByGroup.has(cls))
@@ -1809,7 +1919,7 @@ export function returnAt(source, name) {
1809
1919
  * body and no `return` to find - `const X = () => (<div/>)`. That is a
1810
1920
  * real shape and it gets the concise position.
1811
1921
  */
1812
- const arrow = /=>\s*\(/.exec(source.slice(decl.index, decl.index + 400));
1922
+ const arrow = /=>\s*(?:\(|<[A-Za-z])/.exec(source.slice(decl.index, decl.index + 400));
1813
1923
  if (arrow)
1814
1924
  return decl.index + arrow.index;
1815
1925
  /**
@@ -1833,7 +1943,7 @@ export function returnAt(source, name) {
1833
1943
  return -1;
1834
1944
  }
1835
1945
  }
1836
- return source.search(/return\s*\(/);
1946
+ return source.search(/return\s*(?:\(|<[A-Za-z])/);
1837
1947
  }
1838
1948
  /**
1839
1949
  * THE COMPONENT'S OWN `return (`, not the first one after its name.
@@ -1981,7 +2091,14 @@ function ownReturn(source, declAt) {
1981
2091
  }
1982
2092
  else if (depth === 1 &&
1983
2093
  ch === "r" &&
1984
- /^return\s*\(/.test(source.slice(j))) {
2094
+ /**
2095
+ * `return (` OU `return <div` - o parêntese é estilo, não gramática. O extrator
2096
+ * exigia o parêntese e um `return <div …>;` de linha única não tinha raiz: o
2097
+ * RootWrapper real vestia a última ocorrência de `value-not-read` num look que
2098
+ * nem existia (12/08; 41 arquivos assim no dashboard dele). `<[A-Za-z]` para uma
2099
+ * comparação `return a < b` não virar marcação.
2100
+ */
2101
+ /^return\s*(?:\(|<[A-Za-z])/.test(source.slice(j))) {
1985
2102
  return j;
1986
2103
  }
1987
2104
  }
@@ -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.215";
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.215",
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": {