synthesisui 0.16.402 → 0.16.404
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/commands/add.js +5 -5
- package/dist/font-facts.spec-input.js +24 -0
- package/dist/fonts.js +71 -18
- package/dist/guide.js +2 -2
- package/dist/install-marks.js +1 -1
- package/dist/next-font-weights.js +33 -7
- package/dist/sheet-needed.js +74 -3
- package/package.json +1 -1
package/dist/commands/add.js
CHANGED
|
@@ -8,7 +8,7 @@ import { lockReference } from "../group-role.js";
|
|
|
8
8
|
import { buildGuide } from "../guide.js";
|
|
9
9
|
import { censusScope } from "../measured-scope.js";
|
|
10
10
|
import { recallAvailable } from "../memory/availability.js";
|
|
11
|
-
import {
|
|
11
|
+
import { nextFontFacts } from "../next-font-weights.js";
|
|
12
12
|
import { body as line, section, snippet } from "../output.js";
|
|
13
13
|
import { fetchDesignSystem } from "../registry.js";
|
|
14
14
|
import { repoStateOf } from "../repo-state.js";
|
|
@@ -242,8 +242,8 @@ export async function add(slug, opts) {
|
|
|
242
242
|
* arquivo que o agente dele lê antes de escrever. `null` em projeto que não é Next é a resposta
|
|
243
243
|
* certa, não uma falha - lá não existe `next/font` para ter peso nenhum.
|
|
244
244
|
*/
|
|
245
|
-
const
|
|
246
|
-
await writeFile(join(versionDir, "GUIDE.md"), buildGuide(payload, tools.available,
|
|
245
|
+
const fontFacts = await nextFontFacts(projectRoot);
|
|
246
|
+
await writeFile(join(versionDir, "GUIDE.md"), buildGuide(payload, tools.available, fontFacts), "utf8");
|
|
247
247
|
// 4. stable root re-exports for each CSS artifact → always the active version,
|
|
248
248
|
// so the consumer's @import path never changes across updates
|
|
249
249
|
const cssArtifacts = Object.keys(payload.artifacts).filter((f) => f.endsWith(".css") && !(f === "shadcn.css" && !wantsShadcn));
|
|
@@ -693,7 +693,7 @@ export async function add(slug, opts) {
|
|
|
693
693
|
slug: payload.slug,
|
|
694
694
|
appDir,
|
|
695
695
|
seam: payload.fontSeam,
|
|
696
|
-
|
|
696
|
+
facts: fontFacts,
|
|
697
697
|
declaredWeights: payload.document.foundations.typography.weights,
|
|
698
698
|
})
|
|
699
699
|
: null;
|
|
@@ -771,7 +771,7 @@ export async function add(slug, opts) {
|
|
|
771
771
|
slug: payload.slug,
|
|
772
772
|
appDir: dir,
|
|
773
773
|
seam: payload.fontSeam,
|
|
774
|
-
|
|
774
|
+
facts: fontFacts,
|
|
775
775
|
declaredWeights: payload.document.foundations.typography.weights,
|
|
776
776
|
});
|
|
777
777
|
if (!plan.ok)
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* UM `FontFacts` COMPLETO A PARTIR DO QUE VOCÊ QUER DIZER - a porta que faltava fechar.
|
|
3
|
+
*
|
|
4
|
+
* O SUFIXO `.spec-input` DECLARA A INTENÇÃO NO PONTO DE IMPORTAÇÃO - ver `reachable.spec.ts`: este
|
|
5
|
+
* módulo existe só para alimentar spec, e é uma CATEGORIA, não uma órfã que alguém esqueceu de
|
|
6
|
+
* ligar. Ele nunca entra em código de produto.
|
|
7
|
+
*
|
|
8
|
+
* `buildGuide` e `nextFontSnippet` fecharam "esquecer o ARGUMENTO"; ficou aberto "esquecer um CAMPO
|
|
9
|
+
* dele", e isso custou 35 testes: quando `subsetsOf` nasceu, as fixtures escritas à mão em dez
|
|
10
|
+
* lugares não a tinham, o `guide.spec.ts` parou de CARREGAR, e a suíte seguiu verde com o arquivo
|
|
11
|
+
* inteiro fora da contagem. Só o `tsc` pegava, e o `tsc` dos specs não é o portão de cada rodada.
|
|
12
|
+
*
|
|
13
|
+
* Quem escreve um teste diz só o que importa para ele; os outros campos vêm completos por
|
|
14
|
+
* construção, e um campo novo neste tipo passa a chegar em toda fixture sozinho.
|
|
15
|
+
*/
|
|
16
|
+
export function factsOf(over = {}) {
|
|
17
|
+
const weights = over.weights ?? ["400", "500", "600"];
|
|
18
|
+
const subsets = over.subsets ?? ["latin"];
|
|
19
|
+
return {
|
|
20
|
+
weightsOf: () => weights,
|
|
21
|
+
nameOf: (family) => over.names?.[family] ?? family,
|
|
22
|
+
subsetsOf: () => subsets,
|
|
23
|
+
};
|
|
24
|
+
}
|
package/dist/fonts.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { weightsToEmit } from "./next-font-weights.js";
|
|
1
|
+
import { subsetsToEmit, weightsToEmit, } from "./next-font-weights.js";
|
|
2
2
|
// Fallbacks genéricos do CSS - não são webfonts.
|
|
3
3
|
const GENERIC_FAMILIES = new Set([
|
|
4
4
|
"sans-serif",
|
|
@@ -89,7 +89,7 @@ export function googleFontsHref(families) {
|
|
|
89
89
|
*/
|
|
90
90
|
export const FAMILY_SEAM_PREFIX = "--ds-typography-families-";
|
|
91
91
|
export function nextFontSnippet(input) {
|
|
92
|
-
const { families, slug, appDir = "app", seam,
|
|
92
|
+
const { families, slug, appDir = "app", seam, facts, declaredWeights, } = input;
|
|
93
93
|
/**
|
|
94
94
|
* A ORDEM É DETERMINÍSTICA e os duplicados saem: isto vira uma linha de arquivo gerado, e um
|
|
95
95
|
* conjunto que muda de ordem entre rodadas produz diff onde nada mudou.
|
|
@@ -114,26 +114,70 @@ export function nextFontSnippet(input) {
|
|
|
114
114
|
* suposição que derrubou o build de um projeto real em 08/09. Um arquivo não escrito custa uma
|
|
115
115
|
* frase ao cliente; um arquivo escrito errado custa o build dele.
|
|
116
116
|
*/
|
|
117
|
-
if (!
|
|
117
|
+
if (!facts)
|
|
118
118
|
return { ok: false, why: "manifest-unreadable", families: named };
|
|
119
|
-
|
|
120
|
-
|
|
119
|
+
/**
|
|
120
|
+
* O IDENTIFICADOR SAI DA GRAFIA DO MANIFESTO, nunca de uma regra nossa - ver `FontFacts.nameOf`.
|
|
121
|
+
*
|
|
122
|
+
* A versão anterior fazia `replace(/ /g, "_")` sobre o nome como o documento DELE o escreve, e um
|
|
123
|
+
* `font-family: instrument serif` produzia `import { instrument_serif }`, que não existe. Uma
|
|
124
|
+
* capitalização "por palavra" trocaria esse defeito por outro: `IBM Plex Mono` sairia
|
|
125
|
+
* `Ibm_Plex_Mono`.
|
|
126
|
+
*
|
|
127
|
+
* E NÃO HÁ FALLBACK, de propósito. Uma família sem grafia conhecida é uma família que o manifesto
|
|
128
|
+
* não conhece, e ela segue o mesmo caminho de sempre: sai em `unknown`, sem linha escrita. Manter
|
|
129
|
+
* a heurística como último recurso deixaria a porta aberta para reescrever exatamente o import
|
|
130
|
+
* que quebra o build dele - vigiar em vez de fechar.
|
|
131
|
+
*/
|
|
132
|
+
const importName = (canonical) => canonical.replace(/ /g, "_");
|
|
133
|
+
/**
|
|
134
|
+
* A DEDUPLICAÇÃO É PELA GRAFIA CANÔNICA, e não pelo texto dele - a porta que a canonização
|
|
135
|
+
* ABRIU, achada pela revisão deste PR.
|
|
136
|
+
*
|
|
137
|
+
* Medido: um documento com `display: "Geist"` e `body: "geist, sans-serif"` - dois papéis
|
|
138
|
+
* chegando à mesma família com caixa diferente, que é o normal quando o CSS dele foi escrito por
|
|
139
|
+
* mãos e épocas distintas - produzia
|
|
140
|
+
*
|
|
141
|
+
* import { Geist, Geist } from "next/font/google";
|
|
142
|
+
* SyntaxError: Identifier 'Geist' has already been declared
|
|
143
|
+
*
|
|
144
|
+
* Enquanto o identificador era composto do texto dele, as duas grafias davam dois nomes
|
|
145
|
+
* diferentes (`geist` e `Geist`) - inválidos, mas distintos. Canonizar o nome sem canonizar a
|
|
146
|
+
* CHAVE trocou "dois imports errados" por "um arquivo que não parseia": o mesmo EXIT=1 da
|
|
147
|
+
* `INV-VOLTA-16`, pela porta que este próprio conserto criou.
|
|
148
|
+
*
|
|
149
|
+
* A chave é a grafia do manifesto - a mesma resposta para as duas escritas dele.
|
|
150
|
+
*/
|
|
151
|
+
const seen = new Map(); // canonical family -> const name
|
|
121
152
|
const importNames = [];
|
|
122
153
|
const consts = [];
|
|
123
154
|
/** Família cujos pesos o manifesto não conhece: não se inventa peso para ela. */
|
|
124
155
|
const unknown = [];
|
|
125
156
|
for (const role of roles) {
|
|
126
157
|
const name = firstFamily(families[role] ?? "");
|
|
127
|
-
|
|
128
|
-
|
|
158
|
+
const canonical = facts.nameOf(name);
|
|
159
|
+
if (!canonical || !seen.has(canonical)) {
|
|
160
|
+
const available = facts.weightsOf(name);
|
|
129
161
|
const emit = available ? weightsToEmit(available, wanted) : [];
|
|
130
|
-
if (emit.length === 0) {
|
|
131
|
-
unknown.
|
|
162
|
+
if (emit.length === 0 || !canonical) {
|
|
163
|
+
if (!unknown.includes(name))
|
|
164
|
+
unknown.push(name);
|
|
132
165
|
continue;
|
|
133
166
|
}
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
167
|
+
/**
|
|
168
|
+
* OS SUBSETS TAMBÉM SÃO LIDOS - ver `subsetsToEmit`. Era `["latin"]` fixo, e o `next` reprova
|
|
169
|
+
* subset inexistente na MESMA função em que reprova peso inexistente: 121 das 1911 famílias
|
|
170
|
+
* do manifesto não têm `latin`, então a terceira porta deste módulo levava ao mesmo EXIT=1.
|
|
171
|
+
*
|
|
172
|
+
* Lista vazia significa que a família não declara subset nenhum, e aí a chave não é escrita:
|
|
173
|
+
* o `next` desliga o preload sozinho, e escrever `["latin"]` seria afirmar o que ela não tem.
|
|
174
|
+
*/
|
|
175
|
+
const subsets = subsetsToEmit(facts.subsetsOf(name) ?? []);
|
|
176
|
+
seen.set(canonical, role);
|
|
177
|
+
importNames.push(importName(canonical));
|
|
178
|
+
consts.push(`export const ${role} = ${importName(canonical)}({`, ...(subsets.length > 0
|
|
179
|
+
? [` subsets: [${subsets.map((x) => `"${x}"`).join(", ")}],`]
|
|
180
|
+
: []),
|
|
137
181
|
/**
|
|
138
182
|
* OS PESOS SÃO OS QUE A FAMÍLIA TEM - ver `next-font-weights.ts`.
|
|
139
183
|
*
|
|
@@ -145,7 +189,7 @@ export function nextFontSnippet(input) {
|
|
|
145
189
|
* `["400","500","600"]` de antes derrubou um `create-next-app` inteiro em 08/09. Os dois
|
|
146
190
|
* defeitos são o mesmo erro - afirmar sobre a fonte dele em vez de ler.
|
|
147
191
|
*/
|
|
148
|
-
` weight: [${emit.map((w) => `"${w}"`).join(", ")}],`, ` variable: "--font-ds-${
|
|
192
|
+
` weight: [${emit.map((w) => `"${w}"`).join(", ")}],`, ` variable: "--font-ds-${role}",`, `});`);
|
|
149
193
|
}
|
|
150
194
|
}
|
|
151
195
|
/** Nenhuma família sobreviveu: o manifesto existe e não conhece nenhuma delas. */
|
|
@@ -156,20 +200,29 @@ export function nextFontSnippet(input) {
|
|
|
156
200
|
`import { ${importNames.join(", ")} } from "next/font/google";`,
|
|
157
201
|
...consts,
|
|
158
202
|
];
|
|
159
|
-
|
|
203
|
+
/**
|
|
204
|
+
* A CONST QUE ESTE PAPEL USA - uma resposta só, e é o que impede as três leituras de divergirem.
|
|
205
|
+
*
|
|
206
|
+
* O `layout`, o mapa de CSS e o `roleVar` recompunham a chave cada um do seu jeito a partir do
|
|
207
|
+
* texto dele; com a deduplicação passando a ser canônica, três recomposições são três chances de
|
|
208
|
+
* uma delas citar uma const que o arquivo não exporta.
|
|
209
|
+
*/
|
|
210
|
+
const constOf = (role) => {
|
|
160
211
|
const name = firstFamily(families[role] ?? "");
|
|
161
|
-
|
|
212
|
+
const canonical = facts.nameOf(name);
|
|
213
|
+
return canonical ? seen.get(canonical) : undefined;
|
|
162
214
|
};
|
|
215
|
+
const roleVar = (role) => `--font-ds-${constOf(role)}`;
|
|
163
216
|
/**
|
|
164
217
|
* SÓ AS VAGAS QUE VIRARAM CÓDIGO - uma família descartada por peso desconhecido não pode aparecer
|
|
165
218
|
* no `layout` nem no mapa de CSS: o import citaria uma const que o `fonts.ts` não exporta, e o
|
|
166
219
|
* projeto dele deixaria de compilar por causa da linha que a gente mandou colar.
|
|
167
220
|
*/
|
|
168
|
-
const emitted = roles.filter((role) =>
|
|
221
|
+
const emitted = roles.filter((role) => constOf(role) !== undefined);
|
|
169
222
|
const layout = [
|
|
170
223
|
`// ${appDir}/layout.tsx`,
|
|
171
|
-
`import { ${[...new Set(emitted.map(
|
|
172
|
-
`<body data-ds="${slug}" className={\`${[...new Set(emitted.map((r) => `\${${
|
|
224
|
+
`import { ${[...new Set(emitted.map(constOf))].join(", ")} } from "./fonts";`,
|
|
225
|
+
`<body data-ds="${slug}" className={\`${[...new Set(emitted.map((r) => `\${${constOf(r)}.variable}`))].join(" ")}\`}>`,
|
|
173
226
|
];
|
|
174
227
|
const css = [
|
|
175
228
|
`/* ${appDir}/globals.css - AFTER the tokens.css import */`,
|
package/dist/guide.js
CHANGED
|
@@ -293,7 +293,7 @@ toolsReachable = false,
|
|
|
293
293
|
* essa porta; deixá-la entreaberta um nível acima seria vigiá-la em vez de fechá-la
|
|
294
294
|
* (CLAUDE.md, Parte I, seção 6).
|
|
295
295
|
*/
|
|
296
|
-
|
|
296
|
+
fontFacts) {
|
|
297
297
|
const { document: doc, slug, name, version } = payload;
|
|
298
298
|
const { meta, foundations, motion, components } = doc;
|
|
299
299
|
const semanticRoles = Object.keys(foundations.color.semantic);
|
|
@@ -341,7 +341,7 @@ fontWeights) {
|
|
|
341
341
|
families: foundations.typography.families,
|
|
342
342
|
slug,
|
|
343
343
|
seam: payload.fontSeam,
|
|
344
|
-
|
|
344
|
+
facts: fontFacts,
|
|
345
345
|
/** Os pesos que ELE declara - ver `declaredWeights`. O guia é o arquivo que o agente dele cola. */
|
|
346
346
|
declaredWeights: foundations.typography.weights,
|
|
347
347
|
});
|
package/dist/install-marks.js
CHANGED
|
@@ -170,7 +170,7 @@
|
|
|
170
170
|
* O que o cliente ganha ao rodar `upgrade`: o agente dele no Codex passa a poder PERGUNTAR ao
|
|
171
171
|
* sistema, em vez de só receber as regras e adivinhar o resto.
|
|
172
172
|
*/
|
|
173
|
-
export const MATERIALISER_SINCE = "0.16.
|
|
173
|
+
export const MATERIALISER_SINCE = "0.16.403";
|
|
174
174
|
/**
|
|
175
175
|
* A ÚLTIMA VERSÃO EM QUE O QUE O HOOK RODA MUDOU.
|
|
176
176
|
*
|
|
@@ -22,8 +22,9 @@
|
|
|
22
22
|
* quebra para a fonte que não tem aquele corte. `Instrument Serif` tem exatamente um peso.
|
|
23
23
|
*
|
|
24
24
|
* ONDE MORA A RESPOSTA, e por isso ela não é nossa: o próprio `next` que o projeto dele instalou
|
|
25
|
-
* carrega o manifesto que o `next/font` usa para VALIDAR.
|
|
26
|
-
*
|
|
25
|
+
* carrega o manifesto que o `next/font` usa para VALIDAR. *MEDIÇÃO, e a população é a versão do
|
|
26
|
+
* `next` que o projeto instalou: **1942 famílias** no `create-next-app` de 08/09, **1911** no
|
|
27
|
+
* `next` 16.2.0 deste repositório.* Cada uma com os pesos, estilos e subsets que existem. Ler dali significa que o que a gente
|
|
27
28
|
* emite é exatamente o que o compilador dele aceita - e que uma família nova do Google chega junto
|
|
28
29
|
* com o `npm update` dele, sem release nosso.
|
|
29
30
|
*
|
|
@@ -83,16 +84,25 @@ async function loadManifest(projectRoot) {
|
|
|
83
84
|
* (`"instrument serif"` de um `font-family` em minúsculas é a mesma família), e o manifesto guarda
|
|
84
85
|
* a grafia do Google.
|
|
85
86
|
*/
|
|
86
|
-
export async function
|
|
87
|
+
export async function nextFontFacts(projectRoot) {
|
|
87
88
|
const manifest = await loadManifest(projectRoot);
|
|
88
89
|
if (!manifest)
|
|
89
90
|
return null;
|
|
90
|
-
const
|
|
91
|
+
const weights = new Map();
|
|
92
|
+
const names = new Map();
|
|
93
|
+
const subsets = new Map();
|
|
91
94
|
for (const [family, entry] of Object.entries(manifest)) {
|
|
92
|
-
const
|
|
93
|
-
|
|
95
|
+
const key = family.toLowerCase();
|
|
96
|
+
weights.set(key, (entry?.weights ?? []).filter((w) => /^\d+$/.test(w)));
|
|
97
|
+
names.set(key, family);
|
|
98
|
+
subsets.set(key, entry?.subsets ?? []);
|
|
94
99
|
}
|
|
95
|
-
|
|
100
|
+
const key = (family) => family.trim().toLowerCase();
|
|
101
|
+
return {
|
|
102
|
+
weightsOf: (family) => weights.get(key(family)) ?? null,
|
|
103
|
+
nameOf: (family) => names.get(key(family)) ?? null,
|
|
104
|
+
subsetsOf: (family) => subsets.get(key(family)) ?? null,
|
|
105
|
+
};
|
|
96
106
|
}
|
|
97
107
|
/**
|
|
98
108
|
* OS PESOS A EMITIR para uma família, dados os que o gerador quer e os que existem.
|
|
@@ -112,3 +122,19 @@ export function weightsToEmit(available, wanted = ["400", "500", "600"]) {
|
|
|
112
122
|
const nearest = [...have].sort((a, b) => Math.abs(Number(a) - 400) - Math.abs(Number(b) - 400))[0];
|
|
113
123
|
return [nearest];
|
|
114
124
|
}
|
|
125
|
+
/**
|
|
126
|
+
* OS SUBSETS A PEDIR para uma família, dados os que ela tem.
|
|
127
|
+
*
|
|
128
|
+
* `latin` quando ela o tem - é o que o texto dele usa, e pedir mais baixaria arquivo para nada.
|
|
129
|
+
* Sem `latin` mas com outros, os que ela tem: as 11 famílias nessa situação têm exatamente um
|
|
130
|
+
* (`Chenla` e `Content` são `["khmer"]`), então pedir a lista dela é pedir o único que existe.
|
|
131
|
+
*
|
|
132
|
+
* `[]` quando ela não tem subset nenhum, e aí a chave NÃO é escrita: o `next` desliga o preload
|
|
133
|
+
* sozinho nesse caso, e escrever `["latin"]` seria afirmar sobre a fonte dele um subset que ela não
|
|
134
|
+
* declara - a afirmação que este módulo existe para não fazer.
|
|
135
|
+
*/
|
|
136
|
+
export function subsetsToEmit(available) {
|
|
137
|
+
if (available.includes("latin"))
|
|
138
|
+
return ["latin"];
|
|
139
|
+
return [...available];
|
|
140
|
+
}
|
package/dist/sheet-needed.js
CHANGED
|
@@ -9,18 +9,89 @@ function themeNames(themeCss) {
|
|
|
9
9
|
out.add(m[2]);
|
|
10
10
|
return [...out];
|
|
11
11
|
}
|
|
12
|
+
/**
|
|
13
|
+
* O VALOR DE UM ATRIBUTO DE CLASSE, QUANDO ELE É UMA EXPRESSÃO - e a regra é o ATRIBUTO, não a
|
|
14
|
+
* função.
|
|
15
|
+
*
|
|
16
|
+
* O DEFEITO, medido em 08/09 nas duas árvores do `frontend-hub`: o leitor via `className="…"` e o
|
|
17
|
+
* template direto, e não via `className={cn("…")}`.
|
|
18
|
+
*
|
|
19
|
+
* ```
|
|
20
|
+
* packages/ui/src apps/web-dashboard
|
|
21
|
+
* className=" literal 346 2639
|
|
22
|
+
* className={cn( ou {clsx( 66 239 <- invisíveis
|
|
23
|
+
* ```
|
|
24
|
+
*
|
|
25
|
+
* São **305 sítios** onde o projeto dele escreve classe e a pergunta *"algo aqui precisa da nossa
|
|
26
|
+
* folha?"* era respondida sobre um texto que não continha a resposta. E o erro caía para o lado
|
|
27
|
+
* ERRADO: o cabeçalho deste módulo declara que *"o lado seguro do erro é PRECISA"*, porque um falso
|
|
28
|
+
* positivo mantém o setup sendo pedido e um falso NEGATIVO entrega um componente sem a animação que
|
|
29
|
+
* ele veste. `cn()` é o idioma de quem parte do shadcn, então a metade que faltava era a maior parte
|
|
30
|
+
* de quem chega.
|
|
31
|
+
*
|
|
32
|
+
* POR QUE A REGRA NÃO É O NOME DA FUNÇÃO. Casar `cn|clsx|twMerge` seria a lista dos casos de hoje: o
|
|
33
|
+
* próximo cliente chama a dele de `classes`, `cx` ou nada - passa um ternário direto. O que é
|
|
34
|
+
* estrutural é o ATRIBUTO: dentro do valor de um `class`/`className`, toda string literal é
|
|
35
|
+
* candidata a classe, seja ela argumento de função, ramo de ternário ou pedaço de template.
|
|
36
|
+
*
|
|
37
|
+
* O QUE ISTO NÃO ALCANÇA, e está declarado com o número: a tabela de variantes que mora numa const
|
|
38
|
+
* separada - `cva(...)` e `tv(...)`, medidos em 08/09 como **4 + 41 e 0 + 85** nas duas árvores. Ali
|
|
39
|
+
* as classes chegam ao atributo por VARIÁVEL, e cobri-las exige seguir a variável até a declaração:
|
|
40
|
+
* é outra frente, e o erro dela continua caindo para o lado de cobrar.
|
|
41
|
+
*/
|
|
42
|
+
function attributeExpressions(source) {
|
|
43
|
+
const out = [];
|
|
44
|
+
const ATTR = /class(?:Name)?=\{/g;
|
|
45
|
+
for (const m of source.matchAll(ATTR)) {
|
|
46
|
+
let depth = 0;
|
|
47
|
+
let i = (m.index ?? 0) + m[0].length - 1;
|
|
48
|
+
const start = i;
|
|
49
|
+
/** Aspas contam para NÃO deixar uma chave dentro de string fechar a expressão cedo. */
|
|
50
|
+
let quote = null;
|
|
51
|
+
for (; i < source.length; i += 1) {
|
|
52
|
+
const c = source[i];
|
|
53
|
+
if (quote) {
|
|
54
|
+
if (c === "\\")
|
|
55
|
+
i += 1;
|
|
56
|
+
else if (c === quote)
|
|
57
|
+
quote = null;
|
|
58
|
+
continue;
|
|
59
|
+
}
|
|
60
|
+
if (c === '"' || c === "'" || c === "`")
|
|
61
|
+
quote = c;
|
|
62
|
+
else if (c === "{")
|
|
63
|
+
depth += 1;
|
|
64
|
+
else if (c === "}") {
|
|
65
|
+
depth -= 1;
|
|
66
|
+
if (depth === 0)
|
|
67
|
+
break;
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
if (depth === 0)
|
|
71
|
+
out.push(source.slice(start + 1, i));
|
|
72
|
+
}
|
|
73
|
+
return out;
|
|
74
|
+
}
|
|
12
75
|
/** As classes escritas no código - `hover:animate-shimmer` conta como `animate-shimmer`. */
|
|
13
76
|
function classesIn(source) {
|
|
14
77
|
const out = new Set();
|
|
15
|
-
|
|
16
|
-
const body = m[1] ?? m[2] ?? m[3] ?? m[4] ?? "";
|
|
78
|
+
const collect = (body) => {
|
|
17
79
|
for (const raw of body.split(/\s+/)) {
|
|
18
80
|
if (!raw)
|
|
19
81
|
continue;
|
|
20
82
|
const bare = raw.split(":").pop() ?? raw;
|
|
21
83
|
out.add(bare.replace(/^[!-]/, ""));
|
|
22
84
|
}
|
|
23
|
-
}
|
|
85
|
+
};
|
|
86
|
+
for (const m of source.matchAll(/class(?:Name)?=(?:"([^"]*)"|'([^']*)'|\{`([^`]*)`\}|\{"([^"]*)"\})/g))
|
|
87
|
+
collect(m[1] ?? m[2] ?? m[3] ?? m[4] ?? "");
|
|
88
|
+
/**
|
|
89
|
+
* E TODA STRING DENTRO DA EXPRESSÃO - ver `attributeExpressions`. As três formas de escrever uma
|
|
90
|
+
* string em JS/TS, porque quem escolhe a aspa é o formatador dele, não a gente.
|
|
91
|
+
*/
|
|
92
|
+
for (const expr of attributeExpressions(source))
|
|
93
|
+
for (const lit of expr.matchAll(/"([^"]*)"|'([^']*)'|`([^`]*)`/g))
|
|
94
|
+
collect(lit[1] ?? lit[2] ?? lit[3] ?? "");
|
|
24
95
|
return out;
|
|
25
96
|
}
|
|
26
97
|
/**
|
package/package.json
CHANGED