synthesisui 0.16.26 → 0.16.27
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/doctor.js +1 -0
- package/dist/doctor/scan.js +78 -0
- package/dist/doctor/tokens.js +11 -0
- package/package.json +1 -1
package/dist/commands/doctor.js
CHANGED
|
@@ -191,6 +191,7 @@ const KIND_LABEL = {
|
|
|
191
191
|
radius: "radius",
|
|
192
192
|
spacing: "spacing",
|
|
193
193
|
font: "type",
|
|
194
|
+
motion: "motion",
|
|
194
195
|
};
|
|
195
196
|
/** "1 file", "2 files" - a report that says "1 files read" on its very first
|
|
196
197
|
* line spends credibility before it has said anything. */
|
package/dist/doctor/scan.js
CHANGED
|
@@ -59,6 +59,28 @@ const RADIUS = new RegExp(`(?:border-?[Rr]adius\\s*:\\s*${OPEN}|rounded(?:-[a-z]
|
|
|
59
59
|
const SPACING = new RegExp(`(?:\\b[pmg](?:[trblxy])?-\\[|gap-\\[|(?:padding|margin|gap)(?:[A-Z][a-z]+)?\\s*:\\s*${OPEN})(-?\\d*\\.?\\d+)(px|rem)`, "g");
|
|
60
60
|
/** A font stack written by hand rather than taken from the type scale. */
|
|
61
61
|
const FONT = /font-family\s*:\s*([^;}\n]+)/g;
|
|
62
|
+
/**
|
|
63
|
+
* MOTION, the family the scan never read (item 12, F3).
|
|
64
|
+
*
|
|
65
|
+
* A transition or animation declaration carrying a literal clock - `300ms`,
|
|
66
|
+
* `.2s`, a hand-tuned `cubic-bezier` - is timing decided outside the system,
|
|
67
|
+
* exactly like a hex outside the palette. Anchored to the property so a
|
|
68
|
+
* number elsewhere on the line (a scale factor, a z-index) is never read as
|
|
69
|
+
* time; the value normalizer makes `.3s` and `300ms` one literal, so the
|
|
70
|
+
* system's own duration token gets NAMED, not just flagged.
|
|
71
|
+
*/
|
|
72
|
+
const MOTION_PROP = /\b(?:transition|animation)(?:-(?:duration|delay|timing-function)|Duration|Delay|TimingFunction)?\s*:\s*["'`]?([^;}"'`\n]*)/g;
|
|
73
|
+
const TIME_LITERAL = /(\d*\.?\d+)(ms|s)\b/g;
|
|
74
|
+
const BEZIER = /cubic-bezier\([^)]*\)/g;
|
|
75
|
+
/** `duration-300`, `duration-[350ms]`, `delay-75` - Tailwind's stock clock. */
|
|
76
|
+
const TW_TIME = /\b(?:duration|delay)-(?:\[([^\]]+)\]|(\d+)\b)/g;
|
|
77
|
+
const TW_EASE = /\bease-\[([^\]]+)\]/g;
|
|
78
|
+
/** `animate-fade-up`, `animate-[wiggle_1s_ease]` - checked against the
|
|
79
|
+
* installed vocabulary, so only when the system ships one. */
|
|
80
|
+
const ANIMATE_UTIL = /\banimate-(\[[^\]]+\]|[a-z0-9-]+)/g;
|
|
81
|
+
/** tailwindcss-animate's grammar (the shadcn bridge): `animate-in`/`-out` are
|
|
82
|
+
* composable micro-transition idiom, not a keyframe selection to police. */
|
|
83
|
+
const ANIMATE_IDIOM = new Set(["none", "in", "out"]);
|
|
62
84
|
/**
|
|
63
85
|
* Uses of the system. Coverage is meaningless without them.
|
|
64
86
|
*
|
|
@@ -308,6 +330,61 @@ export function scanSource(file, source, table) {
|
|
|
308
330
|
continue;
|
|
309
331
|
push("font", stack);
|
|
310
332
|
}
|
|
333
|
+
for (const m of line.matchAll(MOTION_PROP)) {
|
|
334
|
+
const value = m[1];
|
|
335
|
+
const base = (m.index ?? 0) + m[0].length - value.length;
|
|
336
|
+
for (const t of value.matchAll(TIME_LITERAL)) {
|
|
337
|
+
// `0s` is idiom (disabling a transition), not a clock chosen by hand.
|
|
338
|
+
if (Number.parseFloat(t[1]) === 0)
|
|
339
|
+
continue;
|
|
340
|
+
push("motion", `${t[1]}${t[2]}`, base + (t.index ?? 0));
|
|
341
|
+
}
|
|
342
|
+
for (const b of value.matchAll(BEZIER)) {
|
|
343
|
+
push("motion", b[0], base + (b.index ?? 0));
|
|
344
|
+
}
|
|
345
|
+
// A `ds-*` animation name that the installed css never declares renders
|
|
346
|
+
// as NOTHING, silently - the keyframe cousin of a phantom token.
|
|
347
|
+
if (table.keyframes.size > 0) {
|
|
348
|
+
for (const w of value.matchAll(/\b(ds-[a-z0-9-]+)\b/g)) {
|
|
349
|
+
if (!table.keyframes.has(w[1]))
|
|
350
|
+
phantoms.push({ name: `@keyframes ${w[1]}`, line: at });
|
|
351
|
+
}
|
|
352
|
+
}
|
|
353
|
+
}
|
|
354
|
+
for (const m of line.matchAll(TW_TIME)) {
|
|
355
|
+
const literal = m[1] ?? `${m[2]}ms`;
|
|
356
|
+
// `duration-[var(--ds-motion-durations-base)]` is the token being USED.
|
|
357
|
+
if (literal.startsWith("var("))
|
|
358
|
+
continue;
|
|
359
|
+
if (Number.parseFloat(literal) === 0)
|
|
360
|
+
continue;
|
|
361
|
+
push("motion", literal, m.index ?? -1);
|
|
362
|
+
}
|
|
363
|
+
for (const m of line.matchAll(TW_EASE)) {
|
|
364
|
+
if (m[1].startsWith("var("))
|
|
365
|
+
continue;
|
|
366
|
+
push("motion", m[1], m.index ?? -1);
|
|
367
|
+
}
|
|
368
|
+
// Vocabulary checks exist only when there is a vocabulary: a system that
|
|
369
|
+
// ships no keyframes left nothing to select from, and silence is honest.
|
|
370
|
+
if (table.keyframes.size > 0 && table.slug) {
|
|
371
|
+
const scope = `ds-${table.slug}-`;
|
|
372
|
+
const vocabulary = new Set([...table.keyframes]
|
|
373
|
+
.filter((k) => k.startsWith(scope))
|
|
374
|
+
.map((k) => k.slice(scope.length)));
|
|
375
|
+
for (const m of line.matchAll(ANIMATE_UTIL)) {
|
|
376
|
+
const name = m[1];
|
|
377
|
+
if (name.startsWith("[")) {
|
|
378
|
+
// An arbitrary animation is a whole hand-rolled shorthand in a
|
|
379
|
+
// class - improvisation by definition once a vocabulary exists.
|
|
380
|
+
push("motion", `animate-${name}`, m.index ?? -1);
|
|
381
|
+
continue;
|
|
382
|
+
}
|
|
383
|
+
if (ANIMATE_IDIOM.has(name) || vocabulary.has(name))
|
|
384
|
+
continue;
|
|
385
|
+
push("motion", `animate-${name}`, m.index ?? -1);
|
|
386
|
+
}
|
|
387
|
+
}
|
|
311
388
|
});
|
|
312
389
|
return {
|
|
313
390
|
file,
|
|
@@ -328,6 +405,7 @@ export function diagnose(files) {
|
|
|
328
405
|
radius: 0,
|
|
329
406
|
spacing: 0,
|
|
330
407
|
font: 0,
|
|
408
|
+
motion: 0,
|
|
331
409
|
};
|
|
332
410
|
for (const f of flat)
|
|
333
411
|
counts[f.kind] += 1;
|
package/dist/doctor/tokens.js
CHANGED
|
@@ -19,6 +19,7 @@ export const EMPTY_TABLE = {
|
|
|
19
19
|
byName: new Map(),
|
|
20
20
|
byValue: new Map(),
|
|
21
21
|
declared: new Set(),
|
|
22
|
+
keyframes: new Set(),
|
|
22
23
|
};
|
|
23
24
|
const hex2 = (n) => Math.max(0, Math.min(255, Math.round(n)))
|
|
24
25
|
.toString(16)
|
|
@@ -91,6 +92,14 @@ function args(body) {
|
|
|
91
92
|
*/
|
|
92
93
|
export function normalizeValue(raw) {
|
|
93
94
|
const v = raw.trim().toLowerCase().replace(/\s+/g, " ");
|
|
95
|
+
// Time lands on milliseconds: `.3s`, `0.3s` and `300ms` are one value, the
|
|
96
|
+
// same way every colour lands on 8-digit hex. Without this, a system that
|
|
97
|
+
// authors `--ds-motion-durations-base: 0.3s` never names an author's `300ms`.
|
|
98
|
+
const time = /^(\d*\.?\d+)(ms|s)$/.exec(v);
|
|
99
|
+
if (time) {
|
|
100
|
+
const ms = Number.parseFloat(time[1]) * (time[2] === "s" ? 1000 : 1);
|
|
101
|
+
return `${ms}ms`;
|
|
102
|
+
}
|
|
94
103
|
const short = /^#([0-9a-f])([0-9a-f])([0-9a-f])([0-9a-f])?$/.exec(v);
|
|
95
104
|
if (short) {
|
|
96
105
|
const d = (c) => c + c;
|
|
@@ -369,6 +378,7 @@ export function buildTable(input) {
|
|
|
369
378
|
byName,
|
|
370
379
|
byValue,
|
|
371
380
|
declared: parseDeclaredNames(input.css),
|
|
381
|
+
keyframes: new Set([...input.css.matchAll(/@keyframes\s+([a-zA-Z0-9_-]+)/g)].map((m) => m[1])),
|
|
372
382
|
};
|
|
373
383
|
}
|
|
374
384
|
/**
|
|
@@ -433,6 +443,7 @@ const FAMILY = {
|
|
|
433
443
|
radius: "--ds-radius-",
|
|
434
444
|
spacing: "--ds-spacing-",
|
|
435
445
|
font: "--ds-typography-",
|
|
446
|
+
motion: "--ds-motion-",
|
|
436
447
|
};
|
|
437
448
|
export function tokenFor(table, literal, kind) {
|
|
438
449
|
const hit = table.byValue.get(normalizeValue(literal));
|
package/package.json
CHANGED